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 android.net; 18 19 import static android.os.Process.CLAT_UID; 20 21 import android.annotation.UnsupportedAppUsage; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.os.SystemClock; 25 import android.util.Slog; 26 import android.util.SparseBooleanArray; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.util.ArrayUtils; 30 31 import libcore.util.EmptyArray; 32 33 import java.io.CharArrayWriter; 34 import java.io.PrintWriter; 35 import java.util.Arrays; 36 import java.util.HashSet; 37 import java.util.Map; 38 import java.util.Objects; 39 40 /** 41 * Collection of active network statistics. Can contain summary details across 42 * all interfaces, or details with per-UID granularity. Internally stores data 43 * as a large table, closely matching {@code /proc/} data format. This structure 44 * optimizes for rapid in-memory comparison, but consider using 45 * {@link NetworkStatsHistory} when persisting. 46 * 47 * @hide 48 */ 49 // @NotThreadSafe 50 public class NetworkStats implements Parcelable { 51 private static final String TAG = "NetworkStats"; 52 /** {@link #iface} value when interface details unavailable. */ 53 public static final String IFACE_ALL = null; 54 /** {@link #uid} value when UID details unavailable. */ 55 public static final int UID_ALL = -1; 56 /** {@link #tag} value matching any tag. */ 57 // TODO: Rename TAG_ALL to TAG_ANY. 58 public static final int TAG_ALL = -1; 59 /** {@link #set} value for all sets combined, not including debug sets. */ 60 public static final int SET_ALL = -1; 61 /** {@link #set} value where background data is accounted. */ 62 public static final int SET_DEFAULT = 0; 63 /** {@link #set} value where foreground data is accounted. */ 64 public static final int SET_FOREGROUND = 1; 65 /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */ 66 public static final int SET_DEBUG_START = 1000; 67 /** Debug {@link #set} value when the VPN stats are moved in. */ 68 public static final int SET_DBG_VPN_IN = 1001; 69 /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */ 70 public static final int SET_DBG_VPN_OUT = 1002; 71 72 /** Include all interfaces when filtering */ 73 public static final String[] INTERFACES_ALL = null; 74 75 /** {@link #tag} value for total data across all tags. */ 76 // TODO: Rename TAG_NONE to TAG_ALL. 77 public static final int TAG_NONE = 0; 78 79 /** {@link #metered} value to account for all metered states. */ 80 public static final int METERED_ALL = -1; 81 /** {@link #metered} value where native, unmetered data is accounted. */ 82 public static final int METERED_NO = 0; 83 /** {@link #metered} value where metered data is accounted. */ 84 public static final int METERED_YES = 1; 85 86 /** {@link #roaming} value to account for all roaming states. */ 87 public static final int ROAMING_ALL = -1; 88 /** {@link #roaming} value where native, non-roaming data is accounted. */ 89 public static final int ROAMING_NO = 0; 90 /** {@link #roaming} value where roaming data is accounted. */ 91 public static final int ROAMING_YES = 1; 92 93 /** {@link #onDefaultNetwork} value to account for all default network states. */ 94 public static final int DEFAULT_NETWORK_ALL = -1; 95 /** {@link #onDefaultNetwork} value to account for usage while not the default network. */ 96 public static final int DEFAULT_NETWORK_NO = 0; 97 /** {@link #onDefaultNetwork} value to account for usage while the default network. */ 98 public static final int DEFAULT_NETWORK_YES = 1; 99 100 /** Denotes a request for stats at the interface level. */ 101 public static final int STATS_PER_IFACE = 0; 102 /** Denotes a request for stats at the interface and UID level. */ 103 public static final int STATS_PER_UID = 1; 104 105 private static final String CLATD_INTERFACE_PREFIX = "v4-"; 106 // Delta between IPv4 header (20b) and IPv6 header (40b). 107 // Used for correct stats accounting on clatd interfaces. 108 private static final int IPV4V6_HEADER_DELTA = 20; 109 110 // TODO: move fields to "mVariable" notation 111 112 /** 113 * {@link SystemClock#elapsedRealtime()} timestamp when this data was 114 * generated. 115 */ 116 private long elapsedRealtime; 117 @UnsupportedAppUsage 118 private int size; 119 @UnsupportedAppUsage 120 private int capacity; 121 @UnsupportedAppUsage 122 private String[] iface; 123 @UnsupportedAppUsage 124 private int[] uid; 125 @UnsupportedAppUsage 126 private int[] set; 127 @UnsupportedAppUsage 128 private int[] tag; 129 @UnsupportedAppUsage 130 private int[] metered; 131 @UnsupportedAppUsage 132 private int[] roaming; 133 @UnsupportedAppUsage 134 private int[] defaultNetwork; 135 @UnsupportedAppUsage 136 private long[] rxBytes; 137 @UnsupportedAppUsage 138 private long[] rxPackets; 139 @UnsupportedAppUsage 140 private long[] txBytes; 141 @UnsupportedAppUsage 142 private long[] txPackets; 143 @UnsupportedAppUsage 144 private long[] operations; 145 146 public static class Entry { 147 @UnsupportedAppUsage 148 public String iface; 149 @UnsupportedAppUsage 150 public int uid; 151 @UnsupportedAppUsage 152 public int set; 153 @UnsupportedAppUsage 154 public int tag; 155 /** 156 * Note that this is only populated w/ the default value when read from /proc or written 157 * to disk. We merge in the correct value when reporting this value to clients of 158 * getSummary(). 159 */ 160 public int metered; 161 /** 162 * Note that this is only populated w/ the default value when read from /proc or written 163 * to disk. We merge in the correct value when reporting this value to clients of 164 * getSummary(). 165 */ 166 public int roaming; 167 /** 168 * Note that this is only populated w/ the default value when read from /proc or written 169 * to disk. We merge in the correct value when reporting this value to clients of 170 * getSummary(). 171 */ 172 public int defaultNetwork; 173 @UnsupportedAppUsage 174 public long rxBytes; 175 @UnsupportedAppUsage 176 public long rxPackets; 177 @UnsupportedAppUsage 178 public long txBytes; 179 @UnsupportedAppUsage 180 public long txPackets; 181 public long operations; 182 183 @UnsupportedAppUsage Entry()184 public Entry() { 185 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 186 } 187 Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)188 public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { 189 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 190 operations); 191 } 192 Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)193 public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, 194 long txBytes, long txPackets, long operations) { 195 this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 196 rxBytes, rxPackets, txBytes, txPackets, operations); 197 } 198 Entry(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)199 public Entry(String iface, int uid, int set, int tag, int metered, int roaming, 200 int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, 201 long operations) { 202 this.iface = iface; 203 this.uid = uid; 204 this.set = set; 205 this.tag = tag; 206 this.metered = metered; 207 this.roaming = roaming; 208 this.defaultNetwork = defaultNetwork; 209 this.rxBytes = rxBytes; 210 this.rxPackets = rxPackets; 211 this.txBytes = txBytes; 212 this.txPackets = txPackets; 213 this.operations = operations; 214 } 215 isNegative()216 public boolean isNegative() { 217 return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0; 218 } 219 isEmpty()220 public boolean isEmpty() { 221 return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0 222 && operations == 0; 223 } 224 add(Entry another)225 public void add(Entry another) { 226 this.rxBytes += another.rxBytes; 227 this.rxPackets += another.rxPackets; 228 this.txBytes += another.txBytes; 229 this.txPackets += another.txPackets; 230 this.operations += another.operations; 231 } 232 233 @Override toString()234 public String toString() { 235 final StringBuilder builder = new StringBuilder(); 236 builder.append("iface=").append(iface); 237 builder.append(" uid=").append(uid); 238 builder.append(" set=").append(setToString(set)); 239 builder.append(" tag=").append(tagToString(tag)); 240 builder.append(" metered=").append(meteredToString(metered)); 241 builder.append(" roaming=").append(roamingToString(roaming)); 242 builder.append(" defaultNetwork=").append(defaultNetworkToString(defaultNetwork)); 243 builder.append(" rxBytes=").append(rxBytes); 244 builder.append(" rxPackets=").append(rxPackets); 245 builder.append(" txBytes=").append(txBytes); 246 builder.append(" txPackets=").append(txPackets); 247 builder.append(" operations=").append(operations); 248 return builder.toString(); 249 } 250 251 @Override equals(Object o)252 public boolean equals(Object o) { 253 if (o instanceof Entry) { 254 final Entry e = (Entry) o; 255 return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered 256 && roaming == e.roaming && defaultNetwork == e.defaultNetwork 257 && rxBytes == e.rxBytes && rxPackets == e.rxPackets 258 && txBytes == e.txBytes && txPackets == e.txPackets 259 && operations == e.operations && iface.equals(e.iface); 260 } 261 return false; 262 } 263 264 @Override hashCode()265 public int hashCode() { 266 return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface); 267 } 268 } 269 270 @UnsupportedAppUsage NetworkStats(long elapsedRealtime, int initialSize)271 public NetworkStats(long elapsedRealtime, int initialSize) { 272 this.elapsedRealtime = elapsedRealtime; 273 this.size = 0; 274 if (initialSize > 0) { 275 this.capacity = initialSize; 276 this.iface = new String[initialSize]; 277 this.uid = new int[initialSize]; 278 this.set = new int[initialSize]; 279 this.tag = new int[initialSize]; 280 this.metered = new int[initialSize]; 281 this.roaming = new int[initialSize]; 282 this.defaultNetwork = new int[initialSize]; 283 this.rxBytes = new long[initialSize]; 284 this.rxPackets = new long[initialSize]; 285 this.txBytes = new long[initialSize]; 286 this.txPackets = new long[initialSize]; 287 this.operations = new long[initialSize]; 288 } else { 289 // Special case for use by NetworkStatsFactory to start out *really* empty. 290 clear(); 291 } 292 } 293 294 @UnsupportedAppUsage NetworkStats(Parcel parcel)295 public NetworkStats(Parcel parcel) { 296 elapsedRealtime = parcel.readLong(); 297 size = parcel.readInt(); 298 capacity = parcel.readInt(); 299 iface = parcel.createStringArray(); 300 uid = parcel.createIntArray(); 301 set = parcel.createIntArray(); 302 tag = parcel.createIntArray(); 303 metered = parcel.createIntArray(); 304 roaming = parcel.createIntArray(); 305 defaultNetwork = parcel.createIntArray(); 306 rxBytes = parcel.createLongArray(); 307 rxPackets = parcel.createLongArray(); 308 txBytes = parcel.createLongArray(); 309 txPackets = parcel.createLongArray(); 310 operations = parcel.createLongArray(); 311 } 312 313 @Override writeToParcel(Parcel dest, int flags)314 public void writeToParcel(Parcel dest, int flags) { 315 dest.writeLong(elapsedRealtime); 316 dest.writeInt(size); 317 dest.writeInt(capacity); 318 dest.writeStringArray(iface); 319 dest.writeIntArray(uid); 320 dest.writeIntArray(set); 321 dest.writeIntArray(tag); 322 dest.writeIntArray(metered); 323 dest.writeIntArray(roaming); 324 dest.writeIntArray(defaultNetwork); 325 dest.writeLongArray(rxBytes); 326 dest.writeLongArray(rxPackets); 327 dest.writeLongArray(txBytes); 328 dest.writeLongArray(txPackets); 329 dest.writeLongArray(operations); 330 } 331 332 @Override clone()333 public NetworkStats clone() { 334 final NetworkStats clone = new NetworkStats(elapsedRealtime, size); 335 NetworkStats.Entry entry = null; 336 for (int i = 0; i < size; i++) { 337 entry = getValues(i, entry); 338 clone.addValues(entry); 339 } 340 return clone; 341 } 342 343 /** 344 * Clear all data stored in this object. 345 */ clear()346 public void clear() { 347 this.capacity = 0; 348 this.iface = EmptyArray.STRING; 349 this.uid = EmptyArray.INT; 350 this.set = EmptyArray.INT; 351 this.tag = EmptyArray.INT; 352 this.metered = EmptyArray.INT; 353 this.roaming = EmptyArray.INT; 354 this.defaultNetwork = EmptyArray.INT; 355 this.rxBytes = EmptyArray.LONG; 356 this.rxPackets = EmptyArray.LONG; 357 this.txBytes = EmptyArray.LONG; 358 this.txPackets = EmptyArray.LONG; 359 this.operations = EmptyArray.LONG; 360 } 361 362 @VisibleForTesting addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets)363 public NetworkStats addIfaceValues( 364 String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { 365 return addValues( 366 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L); 367 } 368 369 @VisibleForTesting addValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)370 public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes, 371 long rxPackets, long txBytes, long txPackets, long operations) { 372 return addValues(new Entry( 373 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); 374 } 375 376 @VisibleForTesting addValues(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)377 public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming, 378 int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, 379 long operations) { 380 return addValues(new Entry( 381 iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets, 382 txBytes, txPackets, operations)); 383 } 384 385 /** 386 * Add new stats entry, copying from given {@link Entry}. The {@link Entry} 387 * object can be recycled across multiple calls. 388 */ addValues(Entry entry)389 public NetworkStats addValues(Entry entry) { 390 if (size >= capacity) { 391 final int newLength = Math.max(size, 10) * 3 / 2; 392 iface = Arrays.copyOf(iface, newLength); 393 uid = Arrays.copyOf(uid, newLength); 394 set = Arrays.copyOf(set, newLength); 395 tag = Arrays.copyOf(tag, newLength); 396 metered = Arrays.copyOf(metered, newLength); 397 roaming = Arrays.copyOf(roaming, newLength); 398 defaultNetwork = Arrays.copyOf(defaultNetwork, newLength); 399 rxBytes = Arrays.copyOf(rxBytes, newLength); 400 rxPackets = Arrays.copyOf(rxPackets, newLength); 401 txBytes = Arrays.copyOf(txBytes, newLength); 402 txPackets = Arrays.copyOf(txPackets, newLength); 403 operations = Arrays.copyOf(operations, newLength); 404 capacity = newLength; 405 } 406 407 setValues(size, entry); 408 size++; 409 410 return this; 411 } 412 setValues(int i, Entry entry)413 private void setValues(int i, Entry entry) { 414 iface[i] = entry.iface; 415 uid[i] = entry.uid; 416 set[i] = entry.set; 417 tag[i] = entry.tag; 418 metered[i] = entry.metered; 419 roaming[i] = entry.roaming; 420 defaultNetwork[i] = entry.defaultNetwork; 421 rxBytes[i] = entry.rxBytes; 422 rxPackets[i] = entry.rxPackets; 423 txBytes[i] = entry.txBytes; 424 txPackets[i] = entry.txPackets; 425 operations[i] = entry.operations; 426 } 427 428 /** 429 * Return specific stats entry. 430 */ 431 @UnsupportedAppUsage getValues(int i, Entry recycle)432 public Entry getValues(int i, Entry recycle) { 433 final Entry entry = recycle != null ? recycle : new Entry(); 434 entry.iface = iface[i]; 435 entry.uid = uid[i]; 436 entry.set = set[i]; 437 entry.tag = tag[i]; 438 entry.metered = metered[i]; 439 entry.roaming = roaming[i]; 440 entry.defaultNetwork = defaultNetwork[i]; 441 entry.rxBytes = rxBytes[i]; 442 entry.rxPackets = rxPackets[i]; 443 entry.txBytes = txBytes[i]; 444 entry.txPackets = txPackets[i]; 445 entry.operations = operations[i]; 446 return entry; 447 } 448 449 /** 450 * If @{code dest} is not equal to @{code src}, copy entry from index @{code src} to index 451 * @{code dest}. 452 */ maybeCopyEntry(int dest, int src)453 private void maybeCopyEntry(int dest, int src) { 454 if (dest == src) return; 455 iface[dest] = iface[src]; 456 uid[dest] = uid[src]; 457 set[dest] = set[src]; 458 tag[dest] = tag[src]; 459 metered[dest] = metered[src]; 460 roaming[dest] = roaming[src]; 461 defaultNetwork[dest] = defaultNetwork[src]; 462 rxBytes[dest] = rxBytes[src]; 463 rxPackets[dest] = rxPackets[src]; 464 txBytes[dest] = txBytes[src]; 465 txPackets[dest] = txPackets[src]; 466 operations[dest] = operations[src]; 467 } 468 getElapsedRealtime()469 public long getElapsedRealtime() { 470 return elapsedRealtime; 471 } 472 setElapsedRealtime(long time)473 public void setElapsedRealtime(long time) { 474 elapsedRealtime = time; 475 } 476 477 /** 478 * Return age of this {@link NetworkStats} object with respect to 479 * {@link SystemClock#elapsedRealtime()}. 480 */ getElapsedRealtimeAge()481 public long getElapsedRealtimeAge() { 482 return SystemClock.elapsedRealtime() - elapsedRealtime; 483 } 484 485 @UnsupportedAppUsage size()486 public int size() { 487 return size; 488 } 489 490 @VisibleForTesting internalSize()491 public int internalSize() { 492 return capacity; 493 } 494 495 @Deprecated combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)496 public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, 497 long txBytes, long txPackets, long operations) { 498 return combineValues( 499 iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, 500 txPackets, operations); 501 } 502 combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)503 public NetworkStats combineValues(String iface, int uid, int set, int tag, 504 long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { 505 return combineValues(new Entry( 506 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); 507 } 508 509 /** 510 * Combine given values with an existing row, or create a new row if 511 * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can 512 * also be used to subtract values from existing rows. 513 */ 514 @UnsupportedAppUsage combineValues(Entry entry)515 public NetworkStats combineValues(Entry entry) { 516 final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered, 517 entry.roaming, entry.defaultNetwork); 518 if (i == -1) { 519 // only create new entry when positive contribution 520 addValues(entry); 521 } else { 522 rxBytes[i] += entry.rxBytes; 523 rxPackets[i] += entry.rxPackets; 524 txBytes[i] += entry.txBytes; 525 txPackets[i] += entry.txPackets; 526 operations[i] += entry.operations; 527 } 528 return this; 529 } 530 531 /** 532 * Combine all values from another {@link NetworkStats} into this object. 533 */ 534 @UnsupportedAppUsage combineAllValues(NetworkStats another)535 public void combineAllValues(NetworkStats another) { 536 NetworkStats.Entry entry = null; 537 for (int i = 0; i < another.size; i++) { 538 entry = another.getValues(i, entry); 539 combineValues(entry); 540 } 541 } 542 543 /** 544 * Find first stats index that matches the requested parameters. 545 */ findIndex(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork)546 public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming, 547 int defaultNetwork) { 548 for (int i = 0; i < size; i++) { 549 if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] 550 && metered == this.metered[i] && roaming == this.roaming[i] 551 && defaultNetwork == this.defaultNetwork[i] 552 && Objects.equals(iface, this.iface[i])) { 553 return i; 554 } 555 } 556 return -1; 557 } 558 559 /** 560 * Find first stats index that matches the requested parameters, starting 561 * search around the hinted index as an optimization. 562 */ 563 @VisibleForTesting findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork, int hintIndex)564 public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming, 565 int defaultNetwork, int hintIndex) { 566 for (int offset = 0; offset < size; offset++) { 567 final int halfOffset = offset / 2; 568 569 // search outwards from hint index, alternating forward and backward 570 final int i; 571 if (offset % 2 == 0) { 572 i = (hintIndex + halfOffset) % size; 573 } else { 574 i = (size + hintIndex - halfOffset - 1) % size; 575 } 576 577 if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] 578 && metered == this.metered[i] && roaming == this.roaming[i] 579 && defaultNetwork == this.defaultNetwork[i] 580 && Objects.equals(iface, this.iface[i])) { 581 return i; 582 } 583 } 584 return -1; 585 } 586 587 /** 588 * Splice in {@link #operations} from the given {@link NetworkStats} based 589 * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, 590 * since operation counts are at data layer. 591 */ spliceOperationsFrom(NetworkStats stats)592 public void spliceOperationsFrom(NetworkStats stats) { 593 for (int i = 0; i < size; i++) { 594 final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i], 595 defaultNetwork[i]); 596 if (j == -1) { 597 operations[i] = 0; 598 } else { 599 operations[i] = stats.operations[j]; 600 } 601 } 602 } 603 604 /** 605 * Return list of unique interfaces known by this data structure. 606 */ getUniqueIfaces()607 public String[] getUniqueIfaces() { 608 final HashSet<String> ifaces = new HashSet<String>(); 609 for (String iface : this.iface) { 610 if (iface != IFACE_ALL) { 611 ifaces.add(iface); 612 } 613 } 614 return ifaces.toArray(new String[ifaces.size()]); 615 } 616 617 /** 618 * Return list of unique UIDs known by this data structure. 619 */ 620 @UnsupportedAppUsage getUniqueUids()621 public int[] getUniqueUids() { 622 final SparseBooleanArray uids = new SparseBooleanArray(); 623 for (int uid : this.uid) { 624 uids.put(uid, true); 625 } 626 627 final int size = uids.size(); 628 final int[] result = new int[size]; 629 for (int i = 0; i < size; i++) { 630 result[i] = uids.keyAt(i); 631 } 632 return result; 633 } 634 635 /** 636 * Return total bytes represented by this snapshot object, usually used when 637 * checking if a {@link #subtract(NetworkStats)} delta passes a threshold. 638 */ 639 @UnsupportedAppUsage getTotalBytes()640 public long getTotalBytes() { 641 final Entry entry = getTotal(null); 642 return entry.rxBytes + entry.txBytes; 643 } 644 645 /** 646 * Return total of all fields represented by this snapshot object. 647 */ 648 @UnsupportedAppUsage getTotal(Entry recycle)649 public Entry getTotal(Entry recycle) { 650 return getTotal(recycle, null, UID_ALL, false); 651 } 652 653 /** 654 * Return total of all fields represented by this snapshot object matching 655 * the requested {@link #uid}. 656 */ 657 @UnsupportedAppUsage getTotal(Entry recycle, int limitUid)658 public Entry getTotal(Entry recycle, int limitUid) { 659 return getTotal(recycle, null, limitUid, false); 660 } 661 662 /** 663 * Return total of all fields represented by this snapshot object matching 664 * the requested {@link #iface}. 665 */ getTotal(Entry recycle, HashSet<String> limitIface)666 public Entry getTotal(Entry recycle, HashSet<String> limitIface) { 667 return getTotal(recycle, limitIface, UID_ALL, false); 668 } 669 670 @UnsupportedAppUsage getTotalIncludingTags(Entry recycle)671 public Entry getTotalIncludingTags(Entry recycle) { 672 return getTotal(recycle, null, UID_ALL, true); 673 } 674 675 /** 676 * Return total of all fields represented by this snapshot object matching 677 * the requested {@link #iface} and {@link #uid}. 678 * 679 * @param limitIface Set of {@link #iface} to include in total; or {@code 680 * null} to include all ifaces. 681 */ getTotal( Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags)682 private Entry getTotal( 683 Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) { 684 final Entry entry = recycle != null ? recycle : new Entry(); 685 686 entry.iface = IFACE_ALL; 687 entry.uid = limitUid; 688 entry.set = SET_ALL; 689 entry.tag = TAG_NONE; 690 entry.metered = METERED_ALL; 691 entry.roaming = ROAMING_ALL; 692 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 693 entry.rxBytes = 0; 694 entry.rxPackets = 0; 695 entry.txBytes = 0; 696 entry.txPackets = 0; 697 entry.operations = 0; 698 699 for (int i = 0; i < size; i++) { 700 final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]); 701 final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i])); 702 703 if (matchesUid && matchesIface) { 704 // skip specific tags, since already counted in TAG_NONE 705 if (tag[i] != TAG_NONE && !includeTags) continue; 706 707 entry.rxBytes += rxBytes[i]; 708 entry.rxPackets += rxPackets[i]; 709 entry.txBytes += txBytes[i]; 710 entry.txPackets += txPackets[i]; 711 entry.operations += operations[i]; 712 } 713 } 714 return entry; 715 } 716 717 /** 718 * Fast path for battery stats. 719 */ getTotalPackets()720 public long getTotalPackets() { 721 long total = 0; 722 for (int i = size-1; i >= 0; i--) { 723 total += rxPackets[i] + txPackets[i]; 724 } 725 return total; 726 } 727 728 /** 729 * Subtract the given {@link NetworkStats}, effectively leaving the delta 730 * between two snapshots in time. Assumes that statistics rows collect over 731 * time, and that none of them have disappeared. 732 */ subtract(NetworkStats right)733 public NetworkStats subtract(NetworkStats right) { 734 return subtract(this, right, null, null); 735 } 736 737 /** 738 * Subtract the two given {@link NetworkStats} objects, returning the delta 739 * between two snapshots in time. Assumes that statistics rows collect over 740 * time, and that none of them have disappeared. 741 * <p> 742 * If counters have rolled backwards, they are clamped to {@code 0} and 743 * reported to the given {@link NonMonotonicObserver}. 744 */ subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie)745 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, 746 NonMonotonicObserver<C> observer, C cookie) { 747 return subtract(left, right, observer, cookie, null); 748 } 749 750 /** 751 * Subtract the two given {@link NetworkStats} objects, returning the delta 752 * between two snapshots in time. Assumes that statistics rows collect over 753 * time, and that none of them have disappeared. 754 * <p> 755 * If counters have rolled backwards, they are clamped to {@code 0} and 756 * reported to the given {@link NonMonotonicObserver}. 757 * <p> 758 * If <var>recycle</var> is supplied, this NetworkStats object will be 759 * reused (and returned) as the result if it is large enough to contain 760 * the data. 761 */ subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle)762 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, 763 NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) { 764 long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime; 765 if (deltaRealtime < 0) { 766 if (observer != null) { 767 observer.foundNonMonotonic(left, -1, right, -1, cookie); 768 } 769 deltaRealtime = 0; 770 } 771 772 // result will have our rows, and elapsed time between snapshots 773 final Entry entry = new Entry(); 774 final NetworkStats result; 775 if (recycle != null && recycle.capacity >= left.size) { 776 result = recycle; 777 result.size = 0; 778 result.elapsedRealtime = deltaRealtime; 779 } else { 780 result = new NetworkStats(deltaRealtime, left.size); 781 } 782 for (int i = 0; i < left.size; i++) { 783 entry.iface = left.iface[i]; 784 entry.uid = left.uid[i]; 785 entry.set = left.set[i]; 786 entry.tag = left.tag[i]; 787 entry.metered = left.metered[i]; 788 entry.roaming = left.roaming[i]; 789 entry.defaultNetwork = left.defaultNetwork[i]; 790 entry.rxBytes = left.rxBytes[i]; 791 entry.rxPackets = left.rxPackets[i]; 792 entry.txBytes = left.txBytes[i]; 793 entry.txPackets = left.txPackets[i]; 794 entry.operations = left.operations[i]; 795 796 // find remote row that matches, and subtract 797 final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, 798 entry.metered, entry.roaming, entry.defaultNetwork, i); 799 if (j != -1) { 800 // Found matching row, subtract remote value. 801 entry.rxBytes -= right.rxBytes[j]; 802 entry.rxPackets -= right.rxPackets[j]; 803 entry.txBytes -= right.txBytes[j]; 804 entry.txPackets -= right.txPackets[j]; 805 entry.operations -= right.operations[j]; 806 } 807 808 if (entry.isNegative()) { 809 if (observer != null) { 810 observer.foundNonMonotonic(left, i, right, j, cookie); 811 } 812 entry.rxBytes = Math.max(entry.rxBytes, 0); 813 entry.rxPackets = Math.max(entry.rxPackets, 0); 814 entry.txBytes = Math.max(entry.txBytes, 0); 815 entry.txPackets = Math.max(entry.txPackets, 0); 816 entry.operations = Math.max(entry.operations, 0); 817 } 818 819 result.addValues(entry); 820 } 821 822 return result; 823 } 824 825 /** 826 * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice. 827 * 828 * <p>This mutates both base and stacked traffic stats, to account respectively for 829 * double-counted traffic and IPv4/IPv6 header size difference. 830 * 831 * <p>For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4 832 * packet on the stacked interface, and once as translated to an IPv6 packet on the 833 * base interface. For correct stats accounting on the base interface, if using xt_qtaguid, 834 * every rx 464xlat packet needs to be subtracted from the root UID on the base interface 835 * (http://b/12249687, http:/b/33681750), and every tx 464xlat packet which was counted onto 836 * clat uid should be ignored. 837 * 838 * As for eBPF, the per uid stats is collected by different hook, the rx packets on base 839 * interface will not be counted. Thus, the adjustment on root uid is not needed. However, the 840 * tx traffic counted in the same way xt_qtaguid does, so the traffic on clat uid still 841 * needs to be ignored. 842 * 843 * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only 844 * {@code ConcurrentHashMap} 845 * @param baseTraffic Traffic on the base interfaces. Will be mutated. 846 * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated. 847 * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. 848 * @param useBpfStats True if eBPF is in use. 849 */ apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats)850 public static void apply464xlatAdjustments(NetworkStats baseTraffic, 851 NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) { 852 // Total 464xlat traffic to subtract from uid 0 on all base interfaces. 853 // stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically. 854 final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size()); 855 856 // For recycling 857 Entry entry = null; 858 Entry adjust = new NetworkStats.Entry(IFACE_ALL, 0, 0, 0, 0, 0, 0, 0L, 0L, 0L, 0L, 0L); 859 860 for (int i = 0; i < stackedTraffic.size; i++) { 861 entry = stackedTraffic.getValues(i, entry); 862 if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) { 863 continue; 864 } 865 final String baseIface = stackedIfaces.get(entry.iface); 866 if (baseIface == null) { 867 continue; 868 } 869 // Subtract xt_qtaguid 464lat rx traffic seen for the root UID on the current base 870 // interface. As for eBPF, the per uid stats is collected by different hook, the rx 871 // packets on base interface will not be counted. 872 adjust.iface = baseIface; 873 if (!useBpfStats) { 874 adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA); 875 adjust.rxPackets = -entry.rxPackets; 876 } 877 adjustments.combineValues(adjust); 878 879 // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet 880 // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after 881 // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes 882 // difference for all packets (http://b/12249687, http:/b/33681750). 883 entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA; 884 entry.txBytes += entry.txPackets * IPV4V6_HEADER_DELTA; 885 stackedTraffic.setValues(i, entry); 886 } 887 888 // Traffic on clat uid is v6 tx traffic that is already counted with app uid on the stacked 889 // v4 interface, so it needs to be removed to avoid double-counting. 890 baseTraffic.removeUids(new int[] {CLAT_UID}); 891 baseTraffic.combineAllValues(adjustments); 892 } 893 894 /** 895 * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice. 896 * 897 * <p>This mutates the object this method is called on. Equivalent to calling 898 * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as 899 * base and stacked traffic. 900 * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. 901 */ apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats)902 public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) { 903 apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats); 904 } 905 906 /** 907 * Return total statistics grouped by {@link #iface}; doesn't mutate the 908 * original structure. 909 */ groupedByIface()910 public NetworkStats groupedByIface() { 911 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 912 913 final Entry entry = new Entry(); 914 entry.uid = UID_ALL; 915 entry.set = SET_ALL; 916 entry.tag = TAG_NONE; 917 entry.metered = METERED_ALL; 918 entry.roaming = ROAMING_ALL; 919 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 920 entry.operations = 0L; 921 922 for (int i = 0; i < size; i++) { 923 // skip specific tags, since already counted in TAG_NONE 924 if (tag[i] != TAG_NONE) continue; 925 926 entry.iface = iface[i]; 927 entry.rxBytes = rxBytes[i]; 928 entry.rxPackets = rxPackets[i]; 929 entry.txBytes = txBytes[i]; 930 entry.txPackets = txPackets[i]; 931 stats.combineValues(entry); 932 } 933 934 return stats; 935 } 936 937 /** 938 * Return total statistics grouped by {@link #uid}; doesn't mutate the 939 * original structure. 940 */ groupedByUid()941 public NetworkStats groupedByUid() { 942 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 943 944 final Entry entry = new Entry(); 945 entry.iface = IFACE_ALL; 946 entry.set = SET_ALL; 947 entry.tag = TAG_NONE; 948 entry.metered = METERED_ALL; 949 entry.roaming = ROAMING_ALL; 950 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 951 952 for (int i = 0; i < size; i++) { 953 // skip specific tags, since already counted in TAG_NONE 954 if (tag[i] != TAG_NONE) continue; 955 956 entry.uid = uid[i]; 957 entry.rxBytes = rxBytes[i]; 958 entry.rxPackets = rxPackets[i]; 959 entry.txBytes = txBytes[i]; 960 entry.txPackets = txPackets[i]; 961 entry.operations = operations[i]; 962 stats.combineValues(entry); 963 } 964 965 return stats; 966 } 967 968 /** 969 * Remove all rows that match one of specified UIDs. 970 */ removeUids(int[] uids)971 public void removeUids(int[] uids) { 972 int nextOutputEntry = 0; 973 for (int i = 0; i < size; i++) { 974 if (!ArrayUtils.contains(uids, uid[i])) { 975 maybeCopyEntry(nextOutputEntry, i); 976 nextOutputEntry++; 977 } 978 } 979 980 size = nextOutputEntry; 981 } 982 983 /** 984 * Only keep entries that match all specified filters. 985 * 986 * <p>This mutates the original structure in place. After this method is called, 987 * size is the number of matching entries, and capacity is the previous capacity. 988 * @param limitUid UID to filter for, or {@link #UID_ALL}. 989 * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}. 990 * @param limitTag Tag to filter for, or {@link #TAG_ALL}. 991 */ filter(int limitUid, String[] limitIfaces, int limitTag)992 public void filter(int limitUid, String[] limitIfaces, int limitTag) { 993 if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) { 994 return; 995 } 996 997 Entry entry = new Entry(); 998 int nextOutputEntry = 0; 999 for (int i = 0; i < size; i++) { 1000 entry = getValues(i, entry); 1001 final boolean matches = 1002 (limitUid == UID_ALL || limitUid == entry.uid) 1003 && (limitTag == TAG_ALL || limitTag == entry.tag) 1004 && (limitIfaces == INTERFACES_ALL 1005 || ArrayUtils.contains(limitIfaces, entry.iface)); 1006 1007 if (matches) { 1008 setValues(nextOutputEntry, entry); 1009 nextOutputEntry++; 1010 } 1011 } 1012 1013 size = nextOutputEntry; 1014 } 1015 dump(String prefix, PrintWriter pw)1016 public void dump(String prefix, PrintWriter pw) { 1017 pw.print(prefix); 1018 pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); 1019 for (int i = 0; i < size; i++) { 1020 pw.print(prefix); 1021 pw.print(" ["); pw.print(i); pw.print("]"); 1022 pw.print(" iface="); pw.print(iface[i]); 1023 pw.print(" uid="); pw.print(uid[i]); 1024 pw.print(" set="); pw.print(setToString(set[i])); 1025 pw.print(" tag="); pw.print(tagToString(tag[i])); 1026 pw.print(" metered="); pw.print(meteredToString(metered[i])); 1027 pw.print(" roaming="); pw.print(roamingToString(roaming[i])); 1028 pw.print(" defaultNetwork="); pw.print(defaultNetworkToString(defaultNetwork[i])); 1029 pw.print(" rxBytes="); pw.print(rxBytes[i]); 1030 pw.print(" rxPackets="); pw.print(rxPackets[i]); 1031 pw.print(" txBytes="); pw.print(txBytes[i]); 1032 pw.print(" txPackets="); pw.print(txPackets[i]); 1033 pw.print(" operations="); pw.println(operations[i]); 1034 } 1035 } 1036 1037 /** 1038 * Return text description of {@link #set} value. 1039 */ setToString(int set)1040 public static String setToString(int set) { 1041 switch (set) { 1042 case SET_ALL: 1043 return "ALL"; 1044 case SET_DEFAULT: 1045 return "DEFAULT"; 1046 case SET_FOREGROUND: 1047 return "FOREGROUND"; 1048 case SET_DBG_VPN_IN: 1049 return "DBG_VPN_IN"; 1050 case SET_DBG_VPN_OUT: 1051 return "DBG_VPN_OUT"; 1052 default: 1053 return "UNKNOWN"; 1054 } 1055 } 1056 1057 /** 1058 * Return text description of {@link #set} value. 1059 */ setToCheckinString(int set)1060 public static String setToCheckinString(int set) { 1061 switch (set) { 1062 case SET_ALL: 1063 return "all"; 1064 case SET_DEFAULT: 1065 return "def"; 1066 case SET_FOREGROUND: 1067 return "fg"; 1068 case SET_DBG_VPN_IN: 1069 return "vpnin"; 1070 case SET_DBG_VPN_OUT: 1071 return "vpnout"; 1072 default: 1073 return "unk"; 1074 } 1075 } 1076 1077 /** 1078 * @return true if the querySet matches the dataSet. 1079 */ setMatches(int querySet, int dataSet)1080 public static boolean setMatches(int querySet, int dataSet) { 1081 if (querySet == dataSet) { 1082 return true; 1083 } 1084 // SET_ALL matches all non-debugging sets. 1085 return querySet == SET_ALL && dataSet < SET_DEBUG_START; 1086 } 1087 1088 /** 1089 * Return text description of {@link #tag} value. 1090 */ tagToString(int tag)1091 public static String tagToString(int tag) { 1092 return "0x" + Integer.toHexString(tag); 1093 } 1094 1095 /** 1096 * Return text description of {@link #metered} value. 1097 */ meteredToString(int metered)1098 public static String meteredToString(int metered) { 1099 switch (metered) { 1100 case METERED_ALL: 1101 return "ALL"; 1102 case METERED_NO: 1103 return "NO"; 1104 case METERED_YES: 1105 return "YES"; 1106 default: 1107 return "UNKNOWN"; 1108 } 1109 } 1110 1111 /** 1112 * Return text description of {@link #roaming} value. 1113 */ roamingToString(int roaming)1114 public static String roamingToString(int roaming) { 1115 switch (roaming) { 1116 case ROAMING_ALL: 1117 return "ALL"; 1118 case ROAMING_NO: 1119 return "NO"; 1120 case ROAMING_YES: 1121 return "YES"; 1122 default: 1123 return "UNKNOWN"; 1124 } 1125 } 1126 1127 /** 1128 * Return text description of {@link #defaultNetwork} value. 1129 */ defaultNetworkToString(int defaultNetwork)1130 public static String defaultNetworkToString(int defaultNetwork) { 1131 switch (defaultNetwork) { 1132 case DEFAULT_NETWORK_ALL: 1133 return "ALL"; 1134 case DEFAULT_NETWORK_NO: 1135 return "NO"; 1136 case DEFAULT_NETWORK_YES: 1137 return "YES"; 1138 default: 1139 return "UNKNOWN"; 1140 } 1141 } 1142 1143 @Override toString()1144 public String toString() { 1145 final CharArrayWriter writer = new CharArrayWriter(); 1146 dump("", new PrintWriter(writer)); 1147 return writer.toString(); 1148 } 1149 1150 @Override describeContents()1151 public int describeContents() { 1152 return 0; 1153 } 1154 1155 @UnsupportedAppUsage 1156 public static final @android.annotation.NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { 1157 @Override 1158 public NetworkStats createFromParcel(Parcel in) { 1159 return new NetworkStats(in); 1160 } 1161 1162 @Override 1163 public NetworkStats[] newArray(int size) { 1164 return new NetworkStats[size]; 1165 } 1166 }; 1167 1168 public interface NonMonotonicObserver<C> { foundNonMonotonic( NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie)1169 public void foundNonMonotonic( 1170 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); foundNonMonotonic( NetworkStats stats, int statsIndex, C cookie)1171 public void foundNonMonotonic( 1172 NetworkStats stats, int statsIndex, C cookie); 1173 } 1174 1175 /** 1176 * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface. 1177 * 1178 * This method should only be called on delta NetworkStats. Do not call this method on a 1179 * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may 1180 * change over time. 1181 * 1182 * This method performs adjustments for one active VPN package and one VPN iface at a time. 1183 * 1184 * It is possible for the VPN software to use multiple underlying networks. This method 1185 * only migrates traffic for the primary underlying network. 1186 * 1187 * @param tunUid uid of the VPN application 1188 * @param tunIface iface of the vpn tunnel 1189 * @param underlyingIface the primary underlying network iface used by the VPN application 1190 * @return true if it successfully adjusts the accounting for VPN, false otherwise 1191 */ migrateTun(int tunUid, String tunIface, String underlyingIface)1192 public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) { 1193 Entry tunIfaceTotal = new Entry(); 1194 Entry underlyingIfaceTotal = new Entry(); 1195 1196 tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal); 1197 1198 // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app. 1199 // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression. 1200 // Negative stats should be avoided. 1201 Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal); 1202 if (pool.isEmpty()) { 1203 return true; 1204 } 1205 Entry moved = 1206 addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool); 1207 deductTrafficFromVpnApp(tunUid, underlyingIface, moved); 1208 1209 if (!moved.isEmpty()) { 1210 Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved=" 1211 + moved); 1212 return false; 1213 } 1214 return true; 1215 } 1216 1217 /** 1218 * Initializes the data used by the migrateTun() method. 1219 * 1220 * This is the first pass iteration which does the following work: 1221 * (1) Adds up all the traffic through the tunUid's underlyingIface 1222 * (both foreground and background). 1223 * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself. 1224 */ tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface, Entry tunIfaceTotal, Entry underlyingIfaceTotal)1225 private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface, 1226 Entry tunIfaceTotal, Entry underlyingIfaceTotal) { 1227 Entry recycle = new Entry(); 1228 for (int i = 0; i < size; i++) { 1229 getValues(i, recycle); 1230 if (recycle.uid == UID_ALL) { 1231 throw new IllegalStateException( 1232 "Cannot adjust VPN accounting on an iface aggregated NetworkStats."); 1233 } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) { 1234 throw new IllegalStateException( 1235 "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*"); 1236 } 1237 1238 if (recycle.uid == tunUid && recycle.tag == TAG_NONE 1239 && Objects.equals(underlyingIface, recycle.iface)) { 1240 underlyingIfaceTotal.add(recycle); 1241 } 1242 1243 if (recycle.uid != tunUid && recycle.tag == TAG_NONE 1244 && Objects.equals(tunIface, recycle.iface)) { 1245 // Add up all tunIface traffic excluding traffic from the vpn app itself. 1246 tunIfaceTotal.add(recycle); 1247 } 1248 } 1249 } 1250 tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal)1251 private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) { 1252 Entry pool = new Entry(); 1253 pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes); 1254 pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets); 1255 pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes); 1256 pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets); 1257 pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations); 1258 return pool; 1259 } 1260 addTrafficToApplications(int tunUid, String tunIface, String underlyingIface, Entry tunIfaceTotal, Entry pool)1261 private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface, 1262 Entry tunIfaceTotal, Entry pool) { 1263 Entry moved = new Entry(); 1264 Entry tmpEntry = new Entry(); 1265 tmpEntry.iface = underlyingIface; 1266 for (int i = 0; i < size; i++) { 1267 // the vpn app is excluded from the redistribution but all moved traffic will be 1268 // deducted from the vpn app (see deductTrafficFromVpnApp below). 1269 if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) { 1270 if (tunIfaceTotal.rxBytes > 0) { 1271 tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes; 1272 } else { 1273 tmpEntry.rxBytes = 0; 1274 } 1275 if (tunIfaceTotal.rxPackets > 0) { 1276 tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets; 1277 } else { 1278 tmpEntry.rxPackets = 0; 1279 } 1280 if (tunIfaceTotal.txBytes > 0) { 1281 tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes; 1282 } else { 1283 tmpEntry.txBytes = 0; 1284 } 1285 if (tunIfaceTotal.txPackets > 0) { 1286 tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets; 1287 } else { 1288 tmpEntry.txPackets = 0; 1289 } 1290 if (tunIfaceTotal.operations > 0) { 1291 tmpEntry.operations = 1292 pool.operations * operations[i] / tunIfaceTotal.operations; 1293 } else { 1294 tmpEntry.operations = 0; 1295 } 1296 tmpEntry.uid = uid[i]; 1297 tmpEntry.tag = tag[i]; 1298 tmpEntry.set = set[i]; 1299 tmpEntry.metered = metered[i]; 1300 tmpEntry.roaming = roaming[i]; 1301 tmpEntry.defaultNetwork = defaultNetwork[i]; 1302 combineValues(tmpEntry); 1303 if (tag[i] == TAG_NONE) { 1304 moved.add(tmpEntry); 1305 // Add debug info 1306 tmpEntry.set = SET_DBG_VPN_IN; 1307 combineValues(tmpEntry); 1308 } 1309 } 1310 } 1311 return moved; 1312 } 1313 deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved)1314 private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) { 1315 // Add debug info 1316 moved.uid = tunUid; 1317 moved.set = SET_DBG_VPN_OUT; 1318 moved.tag = TAG_NONE; 1319 moved.iface = underlyingIface; 1320 moved.metered = METERED_ALL; 1321 moved.roaming = ROAMING_ALL; 1322 moved.defaultNetwork = DEFAULT_NETWORK_ALL; 1323 combineValues(moved); 1324 1325 // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than 1326 // the TAG_NONE traffic. 1327 // 1328 // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO, 1329 // which should be the case as it comes directly from the /proc file. We only blend in the 1330 // roaming data after applying these adjustments, by checking the NetworkIdentity of the 1331 // underlying iface. 1332 int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, 1333 METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); 1334 if (idxVpnBackground != -1) { 1335 tunSubtract(idxVpnBackground, this, moved); 1336 } 1337 1338 int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 1339 METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); 1340 if (idxVpnForeground != -1) { 1341 tunSubtract(idxVpnForeground, this, moved); 1342 } 1343 } 1344 tunSubtract(int i, NetworkStats left, Entry right)1345 private static void tunSubtract(int i, NetworkStats left, Entry right) { 1346 long rxBytes = Math.min(left.rxBytes[i], right.rxBytes); 1347 left.rxBytes[i] -= rxBytes; 1348 right.rxBytes -= rxBytes; 1349 1350 long rxPackets = Math.min(left.rxPackets[i], right.rxPackets); 1351 left.rxPackets[i] -= rxPackets; 1352 right.rxPackets -= rxPackets; 1353 1354 long txBytes = Math.min(left.txBytes[i], right.txBytes); 1355 left.txBytes[i] -= txBytes; 1356 right.txBytes -= txBytes; 1357 1358 long txPackets = Math.min(left.txPackets[i], right.txPackets); 1359 left.txPackets[i] -= txPackets; 1360 right.txPackets -= txPackets; 1361 } 1362 } 1363