Home | History | Annotate | Download | only in net
      1 /*
      2  * Copyright (C) 2016 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.TrafficStats.MB_IN_BYTES;
     20 
     21 import static com.android.internal.util.Preconditions.checkArgument;
     22 
     23 import android.app.usage.NetworkStatsManager;
     24 import android.net.DataUsageRequest;
     25 import android.net.NetworkStats;
     26 import android.net.NetworkStatsHistory;
     27 import android.net.NetworkTemplate;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.HandlerThread;
     31 import android.os.IBinder;
     32 import android.os.Looper;
     33 import android.os.Message;
     34 import android.os.Messenger;
     35 import android.os.Process;
     36 import android.os.RemoteException;
     37 import android.util.ArrayMap;
     38 import android.util.Slog;
     39 import android.util.SparseArray;
     40 
     41 import com.android.internal.annotations.VisibleForTesting;
     42 import com.android.internal.net.VpnInfo;
     43 
     44 import java.util.concurrent.atomic.AtomicInteger;
     45 
     46 /**
     47  * Manages observers of {@link NetworkStats}. Allows observers to be notified when
     48  * data usage has been reported in {@link NetworkStatsService}. An observer can set
     49  * a threshold of how much data it cares about to be notified.
     50  */
     51 class NetworkStatsObservers {
     52     private static final String TAG = "NetworkStatsObservers";
     53     private static final boolean LOGV = false;
     54 
     55     private static final long MIN_THRESHOLD_BYTES = 2 * MB_IN_BYTES;
     56 
     57     private static final int MSG_REGISTER = 1;
     58     private static final int MSG_UNREGISTER = 2;
     59     private static final int MSG_UPDATE_STATS = 3;
     60 
     61     // All access to this map must be done from the handler thread.
     62     // indexed by DataUsageRequest#requestId
     63     private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
     64 
     65     // Sequence number of DataUsageRequests
     66     private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
     67 
     68     // Lazily instantiated when an observer is registered.
     69     private volatile Handler mHandler;
     70 
     71     /**
     72      * Creates a wrapper that contains the caller context and a normalized request.
     73      * The request should be returned to the caller app, and the wrapper should be sent to this
     74      * object through #addObserver by the service handler.
     75      *
     76      * <p>It will register the observer asynchronously, so it is safe to call from any thread.
     77      *
     78      * @return the normalized request wrapped within {@link RequestInfo}.
     79      */
     80     public DataUsageRequest register(DataUsageRequest inputRequest, Messenger messenger,
     81                 IBinder binder, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
     82         DataUsageRequest request = buildRequest(inputRequest);
     83         RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
     84                 accessLevel);
     85 
     86         if (LOGV) Slog.v(TAG, "Registering observer for " + request);
     87         getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
     88         return request;
     89     }
     90 
     91     /**
     92      * Unregister a data usage observer.
     93      *
     94      * <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
     95      */
     96     public void unregister(DataUsageRequest request, int callingUid) {
     97         getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
     98                 request));
     99     }
    100 
    101     /**
    102      * Updates data usage statistics of registered observers and notifies if limits are reached.
    103      *
    104      * <p>It will update stats asynchronously, so it is safe to call from any thread.
    105      */
    106     public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
    107                 ArrayMap<String, NetworkIdentitySet> activeIfaces,
    108                 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
    109                 VpnInfo[] vpnArray, long currentTime) {
    110         StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
    111                 activeUidIfaces, vpnArray, currentTime);
    112         getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
    113     }
    114 
    115     private Handler getHandler() {
    116         if (mHandler == null) {
    117             synchronized (this) {
    118                 if (mHandler == null) {
    119                     if (LOGV) Slog.v(TAG, "Creating handler");
    120                     mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
    121                 }
    122             }
    123         }
    124         return mHandler;
    125     }
    126 
    127     @VisibleForTesting
    128     protected Looper getHandlerLooperLocked() {
    129         HandlerThread handlerThread = new HandlerThread(TAG);
    130         handlerThread.start();
    131         return handlerThread.getLooper();
    132     }
    133 
    134     private Handler.Callback mHandlerCallback = new Handler.Callback() {
    135         @Override
    136         public boolean handleMessage(Message msg) {
    137             switch (msg.what) {
    138                 case MSG_REGISTER: {
    139                     handleRegister((RequestInfo) msg.obj);
    140                     return true;
    141                 }
    142                 case MSG_UNREGISTER: {
    143                     handleUnregister((DataUsageRequest) msg.obj, msg.arg1 /* callingUid */);
    144                     return true;
    145                 }
    146                 case MSG_UPDATE_STATS: {
    147                     handleUpdateStats((StatsContext) msg.obj);
    148                     return true;
    149                 }
    150                 default: {
    151                     return false;
    152                 }
    153             }
    154         }
    155     };
    156 
    157     /**
    158      * Adds a {@link RequestInfo} as an observer.
    159      * Should only be called from the handler thread otherwise there will be a race condition
    160      * on mDataUsageRequests.
    161      */
    162     private void handleRegister(RequestInfo requestInfo) {
    163         mDataUsageRequests.put(requestInfo.mRequest.requestId, requestInfo);
    164     }
    165 
    166     /**
    167      * Removes a {@link DataUsageRequest} if the calling uid is authorized.
    168      * Should only be called from the handler thread otherwise there will be a race condition
    169      * on mDataUsageRequests.
    170      */
    171     private void handleUnregister(DataUsageRequest request, int callingUid) {
    172         RequestInfo requestInfo;
    173         requestInfo = mDataUsageRequests.get(request.requestId);
    174         if (requestInfo == null) {
    175             if (LOGV) Slog.v(TAG, "Trying to unregister unknown request " + request);
    176             return;
    177         }
    178         if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
    179             Slog.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
    180             return;
    181         }
    182 
    183         if (LOGV) Slog.v(TAG, "Unregistering " + request);
    184         mDataUsageRequests.remove(request.requestId);
    185         requestInfo.unlinkDeathRecipient();
    186         requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
    187     }
    188 
    189     private void handleUpdateStats(StatsContext statsContext) {
    190         if (mDataUsageRequests.size() == 0) {
    191             return;
    192         }
    193 
    194         for (int i = 0; i < mDataUsageRequests.size(); i++) {
    195             RequestInfo requestInfo = mDataUsageRequests.valueAt(i);
    196             requestInfo.updateStats(statsContext);
    197         }
    198     }
    199 
    200     private DataUsageRequest buildRequest(DataUsageRequest request) {
    201         // Cap the minimum threshold to a safe default to avoid too many callbacks
    202         long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
    203         if (thresholdInBytes < request.thresholdInBytes) {
    204             Slog.w(TAG, "Threshold was too low for " + request
    205                     + ". Overriding to a safer default of " + thresholdInBytes + " bytes");
    206         }
    207         return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
    208                 request.template, thresholdInBytes);
    209     }
    210 
    211     private RequestInfo buildRequestInfo(DataUsageRequest request,
    212                 Messenger messenger, IBinder binder, int callingUid,
    213                 @NetworkStatsAccess.Level int accessLevel) {
    214         if (accessLevel <= NetworkStatsAccess.Level.USER) {
    215             return new UserUsageRequestInfo(this, request, messenger, binder, callingUid,
    216                     accessLevel);
    217         } else {
    218             // Safety check in case a new access level is added and we forgot to update this
    219             checkArgument(accessLevel >= NetworkStatsAccess.Level.DEVICESUMMARY);
    220             return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
    221                     accessLevel);
    222         }
    223     }
    224 
    225     /**
    226      * Tracks information relevant to a data usage observer.
    227      * It will notice when the calling process dies so we can self-expire.
    228      */
    229     private abstract static class RequestInfo implements IBinder.DeathRecipient {
    230         private final NetworkStatsObservers mStatsObserver;
    231         protected final DataUsageRequest mRequest;
    232         private final Messenger mMessenger;
    233         private final IBinder mBinder;
    234         protected final int mCallingUid;
    235         protected final @NetworkStatsAccess.Level int mAccessLevel;
    236         protected NetworkStatsRecorder mRecorder;
    237         protected NetworkStatsCollection mCollection;
    238 
    239         RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
    240                     Messenger messenger, IBinder binder, int callingUid,
    241                     @NetworkStatsAccess.Level int accessLevel) {
    242             mStatsObserver = statsObserver;
    243             mRequest = request;
    244             mMessenger = messenger;
    245             mBinder = binder;
    246             mCallingUid = callingUid;
    247             mAccessLevel = accessLevel;
    248 
    249             try {
    250                 mBinder.linkToDeath(this, 0);
    251             } catch (RemoteException e) {
    252                 binderDied();
    253             }
    254         }
    255 
    256         @Override
    257         public void binderDied() {
    258             if (LOGV) Slog.v(TAG, "RequestInfo binderDied("
    259                     + mRequest + ", " + mBinder + ")");
    260             mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
    261             callCallback(NetworkStatsManager.CALLBACK_RELEASED);
    262         }
    263 
    264         @Override
    265         public String toString() {
    266             return "RequestInfo from uid:" + mCallingUid
    267                     + " for " + mRequest + " accessLevel:" + mAccessLevel;
    268         }
    269 
    270         private void unlinkDeathRecipient() {
    271             if (mBinder != null) {
    272                 mBinder.unlinkToDeath(this, 0);
    273             }
    274         }
    275 
    276         /**
    277          * Update stats given the samples and interface to identity mappings.
    278          */
    279         private void updateStats(StatsContext statsContext) {
    280             if (mRecorder == null) {
    281                 // First run; establish baseline stats
    282                 resetRecorder();
    283                 recordSample(statsContext);
    284                 return;
    285             }
    286             recordSample(statsContext);
    287 
    288             if (checkStats()) {
    289                 resetRecorder();
    290                 callCallback(NetworkStatsManager.CALLBACK_LIMIT_REACHED);
    291             }
    292         }
    293 
    294         private void callCallback(int callbackType) {
    295             Bundle bundle = new Bundle();
    296             bundle.putParcelable(DataUsageRequest.PARCELABLE_KEY, mRequest);
    297             Message msg = Message.obtain();
    298             msg.what = callbackType;
    299             msg.setData(bundle);
    300             try {
    301                 if (LOGV) {
    302                     Slog.v(TAG, "sending notification " + callbackTypeToName(callbackType)
    303                             + " for " + mRequest);
    304                 }
    305                 mMessenger.send(msg);
    306             } catch (RemoteException e) {
    307                 // May occur naturally in the race of binder death.
    308                 Slog.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
    309             }
    310         }
    311 
    312         private void resetRecorder() {
    313             mRecorder = new NetworkStatsRecorder();
    314             mCollection = mRecorder.getSinceBoot();
    315         }
    316 
    317         protected abstract boolean checkStats();
    318 
    319         protected abstract void recordSample(StatsContext statsContext);
    320 
    321         private String callbackTypeToName(int callbackType) {
    322             switch (callbackType) {
    323                 case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
    324                     return "LIMIT_REACHED";
    325                 case NetworkStatsManager.CALLBACK_RELEASED:
    326                     return "RELEASED";
    327                 default:
    328                     return "UNKNOWN";
    329             }
    330         }
    331     }
    332 
    333     private static class NetworkUsageRequestInfo extends RequestInfo {
    334         NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
    335                     Messenger messenger, IBinder binder, int callingUid,
    336                     @NetworkStatsAccess.Level int accessLevel) {
    337             super(statsObserver, request, messenger, binder, callingUid, accessLevel);
    338         }
    339 
    340         @Override
    341         protected boolean checkStats() {
    342             long bytesSoFar = getTotalBytesForNetwork(mRequest.template);
    343             if (LOGV) {
    344                 Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
    345                         + mRequest.template);
    346             }
    347             if (bytesSoFar > mRequest.thresholdInBytes) {
    348                 return true;
    349             }
    350             return false;
    351         }
    352 
    353         @Override
    354         protected void recordSample(StatsContext statsContext) {
    355             // Recorder does not need to be locked in this context since only the handler
    356             // thread will update it. We pass a null VPN array because usage is aggregated by uid
    357             // for this snapshot, so VPN traffic can't be reattributed to responsible apps.
    358             mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
    359                     null /* vpnArray */, statsContext.mCurrentTime);
    360         }
    361 
    362         /**
    363          * Reads stats matching the given template. {@link NetworkStatsCollection} will aggregate
    364          * over all buckets, which in this case should be only one since we built it big enough
    365          * that it will outlive the caller. If it doesn't, then there will be multiple buckets.
    366          */
    367         private long getTotalBytesForNetwork(NetworkTemplate template) {
    368             NetworkStats stats = mCollection.getSummary(template,
    369                     Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
    370                     mAccessLevel, mCallingUid);
    371             return stats.getTotalBytes();
    372         }
    373     }
    374 
    375     private static class UserUsageRequestInfo extends RequestInfo {
    376         UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
    377                     Messenger messenger, IBinder binder, int callingUid,
    378                     @NetworkStatsAccess.Level int accessLevel) {
    379             super(statsObserver, request, messenger, binder, callingUid, accessLevel);
    380         }
    381 
    382         @Override
    383         protected boolean checkStats() {
    384             int[] uidsToMonitor = mCollection.getRelevantUids(mAccessLevel, mCallingUid);
    385 
    386             for (int i = 0; i < uidsToMonitor.length; i++) {
    387                 long bytesSoFar = getTotalBytesForNetworkUid(mRequest.template, uidsToMonitor[i]);
    388                 if (bytesSoFar > mRequest.thresholdInBytes) {
    389                     return true;
    390                 }
    391             }
    392             return false;
    393         }
    394 
    395         @Override
    396         protected void recordSample(StatsContext statsContext) {
    397             // Recorder does not need to be locked in this context since only the handler
    398             // thread will update it. We pass the VPN info so VPN traffic is reattributed to
    399             // responsible apps.
    400             mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
    401                     statsContext.mVpnArray, statsContext.mCurrentTime);
    402         }
    403 
    404         /**
    405          * Reads all stats matching the given template and uid. Ther history will likely only
    406          * contain one bucket per ident since we build it big enough that it will outlive the
    407          * caller lifetime.
    408          */
    409         private long getTotalBytesForNetworkUid(NetworkTemplate template, int uid) {
    410             try {
    411                 NetworkStatsHistory history = mCollection.getHistory(template, null, uid,
    412                         NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
    413                         NetworkStatsHistory.FIELD_ALL,
    414                         Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
    415                         mAccessLevel, mCallingUid);
    416                 return history.getTotalBytes();
    417             } catch (SecurityException e) {
    418                 if (LOGV) {
    419                     Slog.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
    420                             + uid);
    421                 }
    422                 return 0;
    423             }
    424         }
    425     }
    426 
    427     private static class StatsContext {
    428         NetworkStats mXtSnapshot;
    429         NetworkStats mUidSnapshot;
    430         ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
    431         ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
    432         VpnInfo[] mVpnArray;
    433         long mCurrentTime;
    434 
    435         StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
    436                 ArrayMap<String, NetworkIdentitySet> activeIfaces,
    437                 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
    438                 VpnInfo[] vpnArray, long currentTime) {
    439             mXtSnapshot = xtSnapshot;
    440             mUidSnapshot = uidSnapshot;
    441             mActiveIfaces = activeIfaces;
    442             mActiveUidIfaces = activeUidIfaces;
    443             mVpnArray = vpnArray;
    444             mCurrentTime = currentTime;
    445         }
    446     }
    447 }
    448