1 /* 2 * Copyright (C) 2022 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.netstats; 18 19 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; 20 import static android.net.ConnectivityManager.TYPE_MOBILE; 21 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; 22 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; 23 import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; 24 import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; 25 import static android.net.NetworkStats.SET_DEFAULT; 26 import static android.net.NetworkStats.TAG_NONE; 27 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; 28 29 import android.annotation.NonNull; 30 import android.annotation.StringDef; 31 import android.annotation.SystemApi; 32 import android.net.NetworkIdentity; 33 import android.net.NetworkStatsCollection; 34 import android.net.NetworkStatsHistory; 35 import android.net.NetworkTemplate; 36 import android.os.Environment; 37 import android.util.AtomicFile; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.util.ArtFastDataInput; 41 42 import libcore.io.IoUtils; 43 44 import java.io.BufferedInputStream; 45 import java.io.DataInput; 46 import java.io.DataInputStream; 47 import java.io.File; 48 import java.io.FileInputStream; 49 import java.io.FileNotFoundException; 50 import java.io.IOException; 51 import java.lang.annotation.Retention; 52 import java.lang.annotation.RetentionPolicy; 53 import java.net.ProtocolException; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.HashSet; 57 import java.util.Map; 58 import java.util.Set; 59 60 /** 61 * Helper class to read old version of persistent network statistics. 62 * 63 * The implementation is intended to be modified by OEM partners to 64 * accommodate their custom changes. 65 * 66 * @hide 67 */ 68 @SystemApi(client = MODULE_LIBRARIES) 69 public class NetworkStatsDataMigrationUtils { 70 /** 71 * Prefix of the files which are used to store per network interface statistics. 72 */ 73 public static final String PREFIX_XT = "xt"; 74 /** 75 * Prefix of the files which are used to store per uid statistics. 76 */ 77 public static final String PREFIX_UID = "uid"; 78 /** 79 * Prefix of the files which are used to store per uid tagged traffic statistics. 80 */ 81 public static final String PREFIX_UID_TAG = "uid_tag"; 82 83 /** @hide */ 84 @StringDef(prefix = {"PREFIX_"}, value = { 85 PREFIX_XT, 86 PREFIX_UID, 87 PREFIX_UID_TAG, 88 }) 89 @Retention(RetentionPolicy.SOURCE) 90 public @interface Prefix {} 91 92 private static final Map<String, String> sPrefixLegacyFileNameMap = Map.of( 93 PREFIX_XT, "netstats_xt.bin", 94 PREFIX_UID, "netstats_uid.bin", 95 PREFIX_UID_TAG, "netstats_uid.bin"); 96 97 // These version constants are copied from NetworkStatsCollection/History, which is okay for 98 // OEMs to modify to adapt their own logic. 99 private static class CollectionVersion { 100 static final int VERSION_NETWORK_INIT = 1; 101 102 static final int VERSION_UID_INIT = 1; 103 static final int VERSION_UID_WITH_IDENT = 2; 104 static final int VERSION_UID_WITH_TAG = 3; 105 static final int VERSION_UID_WITH_SET = 4; 106 107 static final int VERSION_UNIFIED_INIT = 16; 108 } 109 110 private static class HistoryVersion { 111 static final int VERSION_INIT = 1; 112 static final int VERSION_ADD_PACKETS = 2; 113 static final int VERSION_ADD_ACTIVE = 3; 114 } 115 116 private static class IdentitySetVersion { 117 static final int VERSION_INIT = 1; 118 static final int VERSION_ADD_ROAMING = 2; 119 static final int VERSION_ADD_NETWORK_ID = 3; 120 static final int VERSION_ADD_METERED = 4; 121 static final int VERSION_ADD_DEFAULT_NETWORK = 5; 122 static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6; 123 static final int VERSION_ADD_SUB_ID = 7; 124 } 125 126 /** 127 * File header magic number: "ANET". The definition is copied from NetworkStatsCollection, 128 * but it is fine for OEM to re-define to their own value to adapt the legacy file reading 129 * logic. 130 */ 131 private static final int FILE_MAGIC = 0x414E4554; 132 /** Default buffer size from BufferedInputStream */ 133 private static final int BUFFER_SIZE = 8192; 134 135 // Constructing this object is not allowed. NetworkStatsDataMigrationUtils()136 private NetworkStatsDataMigrationUtils() { 137 } 138 139 // Used to read files at /data/system/netstats_*.bin. 140 @NonNull getPlatformSystemDir()141 private static File getPlatformSystemDir() { 142 return new File(Environment.getDataDirectory(), "system"); 143 } 144 145 // Used to read files at /data/system/netstats/<tag>.<start>-<end>. 146 @NonNull getPlatformBaseDir()147 private static File getPlatformBaseDir() { 148 File baseDir = new File(getPlatformSystemDir(), "netstats"); 149 baseDir.mkdirs(); 150 return baseDir; 151 } 152 153 // Get /data/system/netstats_*.bin legacy files. Does not check for existence. 154 @NonNull getLegacyBinFileForPrefix(@onNull @refix String prefix)155 private static File getLegacyBinFileForPrefix(@NonNull @Prefix String prefix) { 156 return new File(getPlatformSystemDir(), sPrefixLegacyFileNameMap.get(prefix)); 157 } 158 159 // List /data/system/netstats/[xt|uid|uid_tag].<start>-<end> legacy files. 160 @NonNull getPlatformFileListForPrefix(@onNull @refix String prefix)161 private static ArrayList<File> getPlatformFileListForPrefix(@NonNull @Prefix String prefix) { 162 final ArrayList<File> list = new ArrayList<>(); 163 final File platformFiles = getPlatformBaseDir(); 164 if (platformFiles.exists()) { 165 final String[] files = platformFiles.list(); 166 if (files == null) return list; 167 Arrays.sort(files); 168 for (String name : files) { 169 // Skip when prefix doesn't match. 170 if (!name.startsWith(prefix + ".")) continue; 171 172 list.add(new File(platformFiles, name)); 173 } 174 } 175 return list; 176 } 177 178 /** 179 * Read legacy persisted network stats from disk. 180 * 181 * This function provides the implementation to read legacy network stats 182 * from disk. It is used for migration of legacy network stats into the 183 * stats provided by the Connectivity module. 184 * This function needs to know about the previous format(s) of the network 185 * stats data that might be stored on this device so it can be read and 186 * conserved upon upgrade to Android 13 or above. 187 * 188 * This function will be called multiple times sequentially, all on the 189 * same thread, and will not be called multiple times concurrently. This 190 * function is expected to do a substantial amount of disk access, and 191 * doesn't need to return particularly fast, but the first boot after 192 * an upgrade to Android 13+ will be held until migration is done. As 193 * migration is only necessary once, after the first boot following the 194 * upgrade, this delay is not incurred. 195 * 196 * If this function fails in any way, it should throw an exception. If this 197 * happens, the system can't know about the data that was stored in the 198 * legacy files, but it will still count data usage happening on this 199 * session. On the next boot, the system will try migration again, and 200 * merge the returned data with the data used with the previous session. 201 * The system will only try the migration up to three (3) times. The remaining 202 * count is stored in the netstats_import_legacy_file_needed device config. The 203 * legacy data is never deleted by the mainline module to avoid any possible 204 * data loss. 205 * 206 * It is possible to set the netstats_import_legacy_file_needed device config 207 * to any positive integer to force the module to perform the migration. This 208 * can be achieved by calling the following command before rebooting : 209 * adb shell device_config put connectivity netstats_import_legacy_file_needed 1 210 * 211 * The AOSP implementation provides code to read persisted network stats as 212 * they were written by AOSP prior to Android 13. 213 * OEMs who have used the AOSP implementation of persisting network stats 214 * to disk don't need to change anything. 215 * OEM that had modifications to this format should modify this function 216 * to read from their custom file format or locations if necessary. 217 * 218 * @param prefix Type of data which is being read by the service. 219 * @param bucketDuration Duration of the buckets of the object, in milliseconds. 220 * @return {@link NetworkStatsCollection} instance. 221 */ 222 @NonNull readPlatformCollection( @onNull @refix String prefix, long bucketDuration)223 public static NetworkStatsCollection readPlatformCollection( 224 @NonNull @Prefix String prefix, long bucketDuration) throws IOException { 225 final NetworkStatsCollection.Builder builder = 226 new NetworkStatsCollection.Builder(bucketDuration); 227 228 // Import /data/system/netstats_uid.bin legacy files if exists. 229 switch (prefix) { 230 case PREFIX_UID: 231 case PREFIX_UID_TAG: 232 final File uidFile = getLegacyBinFileForPrefix(prefix); 233 if (uidFile.exists()) { 234 readLegacyUid(builder, uidFile, PREFIX_UID_TAG.equals(prefix) ? true : false); 235 } 236 break; 237 default: 238 // Ignore other types. 239 } 240 241 // Import /data/system/netstats/[xt|uid|uid_tag].<start>-<end> legacy files if exists. 242 final ArrayList<File> platformFiles = getPlatformFileListForPrefix(prefix); 243 for (final File platformFile : platformFiles) { 244 if (platformFile.exists()) { 245 readPlatformCollection(builder, platformFile); 246 } 247 } 248 249 return builder.build(); 250 } 251 readPlatformCollection(@onNull NetworkStatsCollection.Builder builder, @NonNull File file)252 private static void readPlatformCollection(@NonNull NetworkStatsCollection.Builder builder, 253 @NonNull File file) throws IOException { 254 final FileInputStream is = new FileInputStream(file); 255 final ArtFastDataInput dataIn = new ArtFastDataInput(is, BUFFER_SIZE); 256 try { 257 readPlatformCollection(builder, dataIn); 258 } finally { 259 IoUtils.closeQuietly(dataIn); 260 } 261 } 262 263 /** 264 * Helper function to read old version of NetworkStatsCollections that resided in the platform. 265 * 266 * @hide 267 */ 268 @VisibleForTesting readPlatformCollection(@onNull NetworkStatsCollection.Builder builder, @NonNull DataInput in)269 public static void readPlatformCollection(@NonNull NetworkStatsCollection.Builder builder, 270 @NonNull DataInput in) throws IOException { 271 // verify file magic header intact 272 final int magic = in.readInt(); 273 if (magic != FILE_MAGIC) { 274 throw new ProtocolException("unexpected magic: " + magic); 275 } 276 277 final int version = in.readInt(); 278 switch (version) { 279 case CollectionVersion.VERSION_UNIFIED_INIT: { 280 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) 281 final int identSize = in.readInt(); 282 for (int i = 0; i < identSize; i++) { 283 final Set<NetworkIdentity> ident = readPlatformNetworkIdentitySet(in); 284 285 final int size = in.readInt(); 286 for (int j = 0; j < size; j++) { 287 final int uid = in.readInt(); 288 final int set = in.readInt(); 289 final int tag = in.readInt(); 290 291 final NetworkStatsCollection.Key key = new NetworkStatsCollection.Key( 292 ident, uid, set, tag); 293 final NetworkStatsHistory history = readPlatformHistory(in); 294 builder.addEntry(key, history); 295 } 296 } 297 break; 298 } 299 default: { 300 throw new ProtocolException("unexpected version: " + version); 301 } 302 } 303 } 304 305 // Copied from NetworkStatsHistory#DataStreamUtils. readFullLongArray(DataInput in)306 private static long[] readFullLongArray(DataInput in) throws IOException { 307 final int size = in.readInt(); 308 if (size < 0) throw new ProtocolException("negative array size"); 309 final long[] values = new long[size]; 310 for (int i = 0; i < values.length; i++) { 311 values[i] = in.readLong(); 312 } 313 return values; 314 } 315 316 // Copied from NetworkStatsHistory#DataStreamUtils. readVarLongArray(@onNull DataInput in)317 private static long[] readVarLongArray(@NonNull DataInput in) throws IOException { 318 final int size = in.readInt(); 319 if (size == -1) return null; 320 if (size < 0) throw new ProtocolException("negative array size"); 321 final long[] values = new long[size]; 322 for (int i = 0; i < values.length; i++) { 323 values[i] = readVarLong(in); 324 } 325 return values; 326 } 327 328 /** 329 * Read variable-length {@link Long} using protobuf-style approach. 330 */ 331 // Copied from NetworkStatsHistory#DataStreamUtils. readVarLong(DataInput in)332 private static long readVarLong(DataInput in) throws IOException { 333 int shift = 0; 334 long result = 0; 335 while (shift < 64) { 336 byte b = in.readByte(); 337 result |= (long) (b & 0x7F) << shift; 338 if ((b & 0x80) == 0) { 339 return result; 340 } 341 shift += 7; 342 } 343 throw new ProtocolException("malformed var long"); 344 } 345 346 // Copied from NetworkIdentitySet. readOptionalString(DataInput in)347 private static String readOptionalString(DataInput in) throws IOException { 348 if (in.readByte() != 0) { 349 return in.readUTF(); 350 } else { 351 return null; 352 } 353 } 354 355 /** 356 * This is copied from NetworkStatsHistory#NetworkStatsHistory(DataInput in). But it is fine 357 * for OEM to re-write the logic to adapt the legacy file reading. 358 */ 359 @NonNull readPlatformHistory(@onNull DataInput in)360 private static NetworkStatsHistory readPlatformHistory(@NonNull DataInput in) 361 throws IOException { 362 final long bucketDuration; 363 final long[] bucketStart; 364 final long[] rxBytes; 365 final long[] rxPackets; 366 final long[] txBytes; 367 final long[] txPackets; 368 final long[] operations; 369 final int bucketCount; 370 long[] activeTime = new long[0]; 371 372 final int version = in.readInt(); 373 switch (version) { 374 case HistoryVersion.VERSION_INIT: { 375 bucketDuration = in.readLong(); 376 bucketStart = readFullLongArray(in); 377 rxBytes = readFullLongArray(in); 378 rxPackets = new long[bucketStart.length]; 379 txBytes = readFullLongArray(in); 380 txPackets = new long[bucketStart.length]; 381 operations = new long[bucketStart.length]; 382 bucketCount = bucketStart.length; 383 break; 384 } 385 case HistoryVersion.VERSION_ADD_PACKETS: 386 case HistoryVersion.VERSION_ADD_ACTIVE: { 387 bucketDuration = in.readLong(); 388 bucketStart = readVarLongArray(in); 389 activeTime = (version >= HistoryVersion.VERSION_ADD_ACTIVE) 390 ? readVarLongArray(in) 391 : new long[bucketStart.length]; 392 rxBytes = readVarLongArray(in); 393 rxPackets = readVarLongArray(in); 394 txBytes = readVarLongArray(in); 395 txPackets = readVarLongArray(in); 396 operations = readVarLongArray(in); 397 bucketCount = bucketStart.length; 398 break; 399 } 400 default: { 401 throw new ProtocolException("unexpected version: " + version); 402 } 403 } 404 405 final NetworkStatsHistory.Builder historyBuilder = 406 new NetworkStatsHistory.Builder(bucketDuration, bucketCount); 407 for (int i = 0; i < bucketCount; i++) { 408 final NetworkStatsHistory.Entry entry = new NetworkStatsHistory.Entry( 409 bucketStart[i], activeTime[i], 410 rxBytes[i], rxPackets[i], txBytes[i], txPackets[i], operations[i]); 411 historyBuilder.addEntry(entry); 412 } 413 414 return historyBuilder.build(); 415 } 416 417 @NonNull readPlatformNetworkIdentitySet(@onNull DataInput in)418 private static Set<NetworkIdentity> readPlatformNetworkIdentitySet(@NonNull DataInput in) 419 throws IOException { 420 final int version = in.readInt(); 421 final int size = in.readInt(); 422 final Set<NetworkIdentity> set = new HashSet<>(); 423 for (int i = 0; i < size; i++) { 424 if (version <= IdentitySetVersion.VERSION_INIT) { 425 final int ignored = in.readInt(); 426 } 427 final int type = in.readInt(); 428 final int ratType = in.readInt(); 429 final String subscriberId = readOptionalString(in); 430 final String networkId; 431 if (version >= IdentitySetVersion.VERSION_ADD_NETWORK_ID) { 432 networkId = readOptionalString(in); 433 } else { 434 networkId = null; 435 } 436 final boolean roaming; 437 if (version >= IdentitySetVersion.VERSION_ADD_ROAMING) { 438 roaming = in.readBoolean(); 439 } else { 440 roaming = false; 441 } 442 443 final boolean metered; 444 if (version >= IdentitySetVersion.VERSION_ADD_METERED) { 445 metered = in.readBoolean(); 446 } else { 447 // If this is the old data and the type is mobile, treat it as metered. (Note that 448 // if this is a mobile network, TYPE_MOBILE is the only possible type that could be 449 // used.) 450 metered = (type == TYPE_MOBILE); 451 } 452 453 final boolean defaultNetwork; 454 if (version >= IdentitySetVersion.VERSION_ADD_DEFAULT_NETWORK) { 455 defaultNetwork = in.readBoolean(); 456 } else { 457 defaultNetwork = true; 458 } 459 460 final int oemNetCapabilities; 461 if (version >= IdentitySetVersion.VERSION_ADD_OEM_MANAGED_NETWORK) { 462 oemNetCapabilities = in.readInt(); 463 } else { 464 oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO; 465 } 466 467 final int subId; 468 if (version >= IdentitySetVersion.VERSION_ADD_SUB_ID) { 469 subId = in.readInt(); 470 } else { 471 subId = INVALID_SUBSCRIPTION_ID; 472 } 473 474 // Legacy files might contain TYPE_MOBILE_* types which were deprecated in later 475 // releases. For backward compatibility, record them as TYPE_MOBILE instead. 476 final int collapsedLegacyType = getCollapsedLegacyType(type); 477 final NetworkIdentity.Builder builder = new NetworkIdentity.Builder() 478 .setType(collapsedLegacyType) 479 .setSubscriberId(subscriberId) 480 .setWifiNetworkKey(networkId) 481 .setRoaming(roaming).setMetered(metered) 482 .setDefaultNetwork(defaultNetwork) 483 .setOemManaged(oemNetCapabilities) 484 .setSubId(subId); 485 if (type == TYPE_MOBILE && ratType != NetworkTemplate.NETWORK_TYPE_ALL) { 486 builder.setRatType(ratType); 487 } 488 set.add(builder.build()); 489 } 490 return set; 491 } 492 getCollapsedLegacyType(int networkType)493 private static int getCollapsedLegacyType(int networkType) { 494 // The constants are referenced from ConnectivityManager#TYPE_MOBILE_*. 495 switch (networkType) { 496 case TYPE_MOBILE: 497 case TYPE_MOBILE_SUPL: 498 case TYPE_MOBILE_MMS: 499 case TYPE_MOBILE_DUN: 500 case TYPE_MOBILE_HIPRI: 501 case 10 /* TYPE_MOBILE_FOTA */: 502 case 11 /* TYPE_MOBILE_IMS */: 503 case 12 /* TYPE_MOBILE_CBS */: 504 case 14 /* TYPE_MOBILE_IA */: 505 case 15 /* TYPE_MOBILE_EMERGENCY */: 506 return TYPE_MOBILE; 507 } 508 return networkType; 509 } 510 readLegacyUid(@onNull NetworkStatsCollection.Builder builder, @NonNull File uidFile, boolean onlyTaggedData)511 private static void readLegacyUid(@NonNull NetworkStatsCollection.Builder builder, 512 @NonNull File uidFile, boolean onlyTaggedData) throws IOException { 513 final AtomicFile inputFile = new AtomicFile(uidFile); 514 DataInputStream in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); 515 try { 516 readLegacyUid(builder, in, onlyTaggedData); 517 } finally { 518 IoUtils.closeQuietly(in); 519 } 520 } 521 522 /** 523 * Read legacy Uid statistics file format into the collection. 524 * 525 * This is copied from {@code NetworkStatsCollection#readLegacyUid}. 526 * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}. 527 * 528 * @param taggedData whether to read only tagged data (true) or only non-tagged data 529 * (false). For legacy uid files, the tagged data was stored in 530 * the same binary file with non-tagged data. But in later releases, 531 * these data should be kept in different recorders. 532 * @hide 533 */ 534 @VisibleForTesting readLegacyUid(@onNull NetworkStatsCollection.Builder builder, @NonNull DataInput in, boolean taggedData)535 public static void readLegacyUid(@NonNull NetworkStatsCollection.Builder builder, 536 @NonNull DataInput in, boolean taggedData) throws IOException { 537 try { 538 // verify file magic header intact 539 final int magic = in.readInt(); 540 if (magic != FILE_MAGIC) { 541 throw new ProtocolException("unexpected magic: " + magic); 542 } 543 544 final int version = in.readInt(); 545 switch (version) { 546 case CollectionVersion.VERSION_UID_INIT: { 547 // uid := size *(UID NetworkStatsHistory) 548 // drop this data version, since we don't have a good 549 // mapping into NetworkIdentitySet. 550 break; 551 } 552 case CollectionVersion.VERSION_UID_WITH_IDENT: { 553 // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) 554 // drop this data version, since this version only existed 555 // for a short time. 556 break; 557 } 558 case CollectionVersion.VERSION_UID_WITH_TAG: 559 case CollectionVersion.VERSION_UID_WITH_SET: { 560 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) 561 final int identSize = in.readInt(); 562 for (int i = 0; i < identSize; i++) { 563 final Set<NetworkIdentity> ident = readPlatformNetworkIdentitySet(in); 564 565 final int size = in.readInt(); 566 for (int j = 0; j < size; j++) { 567 final int uid = in.readInt(); 568 final int set = (version >= CollectionVersion.VERSION_UID_WITH_SET) 569 ? in.readInt() 570 : SET_DEFAULT; 571 final int tag = in.readInt(); 572 573 final NetworkStatsCollection.Key key = new NetworkStatsCollection.Key( 574 ident, uid, set, tag); 575 final NetworkStatsHistory history = readPlatformHistory(in); 576 577 if ((tag == TAG_NONE) != taggedData) { 578 builder.addEntry(key, history); 579 } 580 } 581 } 582 break; 583 } 584 default: { 585 throw new ProtocolException("unknown version: " + version); 586 } 587 } 588 } catch (FileNotFoundException | ProtocolException e) { 589 // missing stats is okay, probably first boot 590 } 591 } 592 } 593