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 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 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 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.server.BpfNetMaps; 34 35 import java.io.IOException; 36 import java.net.ProtocolException; 37 import java.util.Map; 38 import java.util.concurrent.ConcurrentHashMap; 39 40 /** 41 * Creates {@link NetworkStats} instances by parsing various {@code /proc/} 42 * files as needed. 43 * 44 * @hide 45 */ 46 public class NetworkStatsFactory { 47 static { 48 System.loadLibrary("service-connectivity"); 49 } 50 51 private static final String TAG = "NetworkStatsFactory"; 52 53 private final Context mContext; 54 55 private final BpfNetMaps mBpfNetMaps; 56 57 /** 58 * Guards persistent data access in this class 59 * 60 * <p>In order to prevent deadlocks, critical sections protected by this lock SHALL NOT call out 61 * to other code that will acquire other locks within the system server. See b/134244752. 62 */ 63 private final Object mPersistentDataLock = new Object(); 64 65 /** Set containing info about active VPNs and their underlying networks. */ 66 private volatile UnderlyingNetworkInfo[] mUnderlyingNetworkInfos = new UnderlyingNetworkInfo[0]; 67 68 // A persistent snapshot of cumulative stats since device start 69 @GuardedBy("mPersistentDataLock") 70 private NetworkStats mPersistSnapshot; 71 72 // The persistent snapshot of tun and 464xlat adjusted stats since device start 73 @GuardedBy("mPersistentDataLock") 74 private NetworkStats mTunAnd464xlatAdjustedStats; 75 76 private final Dependencies mDeps; 77 /** 78 * Dependencies of NetworkStatsFactory, for injection in tests. 79 */ 80 @VisibleForTesting 81 public static class Dependencies { 82 /** 83 * Parse detailed statistics from bpf into given {@link NetworkStats} object. Values 84 * are expected to monotonically increase since device boot. 85 */ 86 @NonNull getNetworkStatsDetail()87 public NetworkStats getNetworkStatsDetail() throws IOException { 88 final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); 89 final int ret = nativeReadNetworkStatsDetail(stats); 90 if (ret != 0) { 91 throw new IOException("Failed to parse network stats"); 92 } 93 return stats; 94 } 95 /** 96 * Parse device summary statistics from bpf into given {@link NetworkStats} object. Values 97 * are expected to monotonically increase since device boot. 98 */ 99 @NonNull getNetworkStatsDev()100 public NetworkStats getNetworkStatsDev() throws IOException { 101 final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); 102 final int ret = nativeReadNetworkStatsDev(stats); 103 if (ret != 0) { 104 throw new IOException("Failed to parse bpf iface stats"); 105 } 106 return stats; 107 } 108 109 /** Create a new {@link BpfNetMaps}. */ createBpfNetMaps(@onNull Context ctx)110 public BpfNetMaps createBpfNetMaps(@NonNull Context ctx) { 111 return new BpfNetMaps(ctx); 112 } 113 } 114 115 /** 116 * (Stacked interface) -> (base interface) association for all connected ifaces since boot. 117 * 118 * Because counters must never roll backwards, once a given interface is stacked on top of an 119 * underlying interface, the stacked interface can never be stacked on top of 120 * another interface. */ 121 private final ConcurrentHashMap<String, String> mStackedIfaces 122 = new ConcurrentHashMap<>(); 123 124 /** Informs the factory of a new stacked interface. */ noteStackedIface(String stackedIface, String baseIface)125 public void noteStackedIface(String stackedIface, String baseIface) { 126 if (stackedIface != null && baseIface != null) { 127 mStackedIfaces.put(stackedIface, baseIface); 128 } 129 } 130 131 /** 132 * Set active VPN information for data usage migration purposes 133 * 134 * <p>Traffic on TUN-based VPNs inherently all appear to be originated from the VPN providing 135 * app's UID. This method is used to support migration of VPN data usage, ensuring data is 136 * accurately billed to the real owner of the traffic. 137 * 138 * @param vpnArray The snapshot of the currently-running VPNs. 139 */ updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray)140 public void updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray) { 141 mUnderlyingNetworkInfos = vpnArray.clone(); 142 } 143 144 /** 145 * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}. 146 * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map) 147 */ apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic)148 public void apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic) { 149 NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces); 150 } 151 NetworkStatsFactory(@onNull Context ctx)152 public NetworkStatsFactory(@NonNull Context ctx) { 153 this(ctx, new Dependencies()); 154 } 155 156 @VisibleForTesting NetworkStatsFactory(@onNull Context ctx, Dependencies deps)157 public NetworkStatsFactory(@NonNull Context ctx, Dependencies deps) { 158 mBpfNetMaps = deps.createBpfNetMaps(ctx); 159 synchronized (mPersistentDataLock) { 160 mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1); 161 mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1); 162 } 163 mContext = ctx; 164 mDeps = deps; 165 } 166 167 /** 168 * Parse and return interface-level summary {@link NetworkStats}. Designed 169 * to return only IP layer traffic. Values monotonically increase since 170 * device boot, and may include details about inactive interfaces. 171 */ readNetworkStatsSummaryXt()172 public NetworkStats readNetworkStatsSummaryXt() throws IOException { 173 return mDeps.getNetworkStatsDev(); 174 } 175 readNetworkStatsDetail()176 public NetworkStats readNetworkStatsDetail() throws IOException { 177 return readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL); 178 } 179 180 @GuardedBy("mPersistentDataLock") requestSwapActiveStatsMapLocked()181 private void requestSwapActiveStatsMapLocked() throws IOException { 182 try { 183 // Do a active map stats swap. Once the swap completes, this code 184 // can read and clean the inactive map without races. 185 mBpfNetMaps.swapActiveStatsMap(); 186 } catch (ServiceSpecificException e) { 187 throw new IOException(e); 188 } 189 } 190 191 /** 192 * Reads the detailed UID stats based on the provided parameters 193 * 194 * @param limitUid the UID to limit this query to 195 * @param limitIfaces the interfaces to limit this query to. Use {@link 196 * NetworkStats.INTERFACES_ALL} to select all interfaces 197 * @param limitTag the tags to limit this query to 198 * @return the NetworkStats instance containing network statistics at the present time. 199 */ readNetworkStatsDetail( int limitUid, String[] limitIfaces, int limitTag)200 public NetworkStats readNetworkStatsDetail( 201 int limitUid, String[] limitIfaces, int limitTag) throws IOException { 202 // In order to prevent deadlocks, anything protected by this lock MUST NOT call out to other 203 // code that will acquire other locks within the system server. See b/134244752. 204 synchronized (mPersistentDataLock) { 205 // Take a reference. If this gets swapped out, we still have the old reference. 206 final UnderlyingNetworkInfo[] vpnArray = mUnderlyingNetworkInfos; 207 // Take a defensive copy. mPersistSnapshot is mutated in some cases below 208 final NetworkStats prev = mPersistSnapshot.clone(); 209 210 requestSwapActiveStatsMapLocked(); 211 // Stats are always read from the inactive map, so they must be read after the 212 // swap 213 final NetworkStats stats = mDeps.getNetworkStatsDetail(); 214 // BPF stats are incremental; fold into mPersistSnapshot. 215 mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime()); 216 mPersistSnapshot.combineAllValues(stats); 217 218 NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray); 219 220 // Filter return values 221 adjustedStats.filter(limitUid, limitIfaces, limitTag); 222 return adjustedStats; 223 } 224 } 225 226 @GuardedBy("mPersistentDataLock") adjustForTunAnd464Xlat(NetworkStats uidDetailStats, NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray)227 private NetworkStats adjustForTunAnd464Xlat(NetworkStats uidDetailStats, 228 NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray) { 229 // Calculate delta from last snapshot 230 final NetworkStats delta = uidDetailStats.subtract(previousStats); 231 232 // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only 233 // network, the overhead is their fault. 234 // No locking here: apply464xlatAdjustments behaves fine with an add-only 235 // ConcurrentHashMap. 236 delta.apply464xlatAdjustments(mStackedIfaces); 237 238 // Migrate data usage over a VPN to the TUN network. 239 for (UnderlyingNetworkInfo info : vpnArray) { 240 delta.migrateTun(info.getOwnerUid(), info.getInterface(), 241 info.getUnderlyingInterfaces()); 242 // Filter out debug entries as that may lead to over counting. 243 delta.filterDebugEntries(); 244 } 245 246 // Update mTunAnd464xlatAdjustedStats with migrated delta. 247 mTunAnd464xlatAdjustedStats.combineAllValues(delta); 248 mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime()); 249 250 return mTunAnd464xlatAdjustedStats.clone(); 251 } 252 253 /** 254 * Remove stats from {@code mPersistSnapshot} and {@code mTunAnd464xlatAdjustedStats} for the 255 * given uids. 256 */ removeUidsLocked(int[] uids)257 public void removeUidsLocked(int[] uids) { 258 synchronized (mPersistentDataLock) { 259 mPersistSnapshot.removeUids(uids); 260 mTunAnd464xlatAdjustedStats.removeUids(uids); 261 } 262 } 263 assertEquals(NetworkStats expected, NetworkStats actual)264 public void assertEquals(NetworkStats expected, NetworkStats actual) { 265 if (expected.size() != actual.size()) { 266 throw new AssertionError( 267 "Expected size " + expected.size() + ", actual size " + actual.size()); 268 } 269 270 NetworkStats.Entry expectedRow = null; 271 NetworkStats.Entry actualRow = null; 272 for (int i = 0; i < expected.size(); i++) { 273 expectedRow = expected.getValues(i, expectedRow); 274 actualRow = actual.getValues(i, actualRow); 275 if (!expectedRow.equals(actualRow)) { 276 throw new AssertionError( 277 "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow); 278 } 279 } 280 } 281 282 /** 283 * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming 284 * format like {@code 0x7fffffff00000000}. 285 */ kernelToTag(String string)286 public static int kernelToTag(String string) { 287 int length = string.length(); 288 if (length > 10) { 289 return Long.decode(string.substring(0, length - 8)).intValue(); 290 } else { 291 return 0; 292 } 293 } 294 295 /** 296 * Parse statistics from file into given {@link NetworkStats} object. Values 297 * are expected to monotonically increase since device boot. 298 */ 299 @VisibleForTesting nativeReadNetworkStatsDetail(NetworkStats stats)300 public static native int nativeReadNetworkStatsDetail(NetworkStats stats); 301 302 @VisibleForTesting nativeReadNetworkStatsDev(NetworkStats stats)303 public static native int nativeReadNetworkStatsDev(NetworkStats stats); 304 protocolExceptionWithCause(String message, Throwable cause)305 private static ProtocolException protocolExceptionWithCause(String message, Throwable cause) { 306 ProtocolException pe = new ProtocolException(message); 307 pe.initCause(cause); 308 return pe; 309 } 310 } 311