Home | History | Annotate | Download | only in tethering
      1 /*
      2  * Copyright (C) 2017 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.connectivity.tethering;
     18 
     19 import static android.net.ConnectivityManager.getNetworkTypeName;
     20 import static android.net.ConnectivityManager.TYPE_NONE;
     21 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
     22 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
     23 
     24 import android.content.Context;
     25 import android.os.Handler;
     26 import android.os.Looper;
     27 import android.os.Process;
     28 import android.net.ConnectivityManager;
     29 import android.net.ConnectivityManager.NetworkCallback;
     30 import android.net.IpPrefix;
     31 import android.net.LinkAddress;
     32 import android.net.LinkProperties;
     33 import android.net.Network;
     34 import android.net.NetworkCapabilities;
     35 import android.net.NetworkRequest;
     36 import android.net.NetworkState;
     37 import android.net.util.NetworkConstants;
     38 import android.net.util.PrefixUtils;
     39 import android.net.util.SharedLog;
     40 import android.util.Log;
     41 
     42 import com.android.internal.annotations.VisibleForTesting;
     43 import com.android.internal.util.StateMachine;
     44 
     45 import java.util.Collections;
     46 import java.util.HashMap;
     47 import java.util.HashSet;
     48 import java.util.Set;
     49 
     50 
     51 /**
     52  * A class to centralize all the network and link properties information
     53  * pertaining to the current and any potential upstream network.
     54  *
     55  * Calling #start() registers two callbacks: one to track the system default
     56  * network and a second to observe all networks.  The latter is necessary
     57  * while the expression of preferred upstreams remains a list of legacy
     58  * connectivity types.  In future, this can be revisited.
     59  *
     60  * The methods and data members of this class are only to be accessed and
     61  * modified from the tethering master state machine thread. Any other
     62  * access semantics would necessitate the addition of locking.
     63  *
     64  * TODO: Move upstream selection logic here.
     65  *
     66  * All callback methods are run on the same thread as the specified target
     67  * state machine.  This class does not require locking when accessed from this
     68  * thread.  Access from other threads is not advised.
     69  *
     70  * @hide
     71  */
     72 public class UpstreamNetworkMonitor {
     73     private static final String TAG = UpstreamNetworkMonitor.class.getSimpleName();
     74     private static final boolean DBG = false;
     75     private static final boolean VDBG = false;
     76 
     77     public static final int EVENT_ON_AVAILABLE      = 1;
     78     public static final int EVENT_ON_CAPABILITIES   = 2;
     79     public static final int EVENT_ON_LINKPROPERTIES = 3;
     80     public static final int EVENT_ON_LOST           = 4;
     81     public static final int NOTIFY_LOCAL_PREFIXES   = 10;
     82 
     83     private static final int CALLBACK_LISTEN_ALL = 1;
     84     private static final int CALLBACK_TRACK_DEFAULT = 2;
     85     private static final int CALLBACK_MOBILE_REQUEST = 3;
     86 
     87     private final Context mContext;
     88     private final SharedLog mLog;
     89     private final StateMachine mTarget;
     90     private final Handler mHandler;
     91     private final int mWhat;
     92     private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
     93     private HashSet<IpPrefix> mLocalPrefixes;
     94     private ConnectivityManager mCM;
     95     private NetworkCallback mListenAllCallback;
     96     private NetworkCallback mDefaultNetworkCallback;
     97     private NetworkCallback mMobileNetworkCallback;
     98     private boolean mDunRequired;
     99     // The current system default network (not really used yet).
    100     private Network mDefaultInternetNetwork;
    101     // The current upstream network used for tethering.
    102     private Network mTetheringUpstreamNetwork;
    103 
    104     public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
    105         mContext = ctx;
    106         mTarget = tgt;
    107         mHandler = mTarget.getHandler();
    108         mLog = log.forSubComponent(TAG);
    109         mWhat = what;
    110         mLocalPrefixes = new HashSet<>();
    111     }
    112 
    113     @VisibleForTesting
    114     public UpstreamNetworkMonitor(
    115             ConnectivityManager cm, StateMachine tgt, SharedLog log, int what) {
    116         this((Context) null, tgt, log, what);
    117         mCM = cm;
    118     }
    119 
    120     public void start() {
    121         stop();
    122 
    123         final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
    124                 .clearCapabilities().build();
    125         mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL);
    126         cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler);
    127 
    128         mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_TRACK_DEFAULT);
    129         cm().registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
    130     }
    131 
    132     public void stop() {
    133         releaseMobileNetworkRequest();
    134 
    135         releaseCallback(mDefaultNetworkCallback);
    136         mDefaultNetworkCallback = null;
    137         mDefaultInternetNetwork = null;
    138 
    139         releaseCallback(mListenAllCallback);
    140         mListenAllCallback = null;
    141 
    142         mTetheringUpstreamNetwork = null;
    143         mNetworkMap.clear();
    144     }
    145 
    146     public void updateMobileRequiresDun(boolean dunRequired) {
    147         final boolean valueChanged = (mDunRequired != dunRequired);
    148         mDunRequired = dunRequired;
    149         if (valueChanged && mobileNetworkRequested()) {
    150             releaseMobileNetworkRequest();
    151             registerMobileNetworkRequest();
    152         }
    153     }
    154 
    155     public boolean mobileNetworkRequested() {
    156         return (mMobileNetworkCallback != null);
    157     }
    158 
    159     public void registerMobileNetworkRequest() {
    160         if (mMobileNetworkCallback != null) {
    161             mLog.e("registerMobileNetworkRequest() already registered");
    162             return;
    163         }
    164 
    165         // The following use of the legacy type system cannot be removed until
    166         // after upstream selection no longer finds networks by legacy type.
    167         // See also http://b/34364553 .
    168         final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
    169 
    170         final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
    171                 .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
    172                 .build();
    173 
    174         // The existing default network and DUN callbacks will be notified.
    175         // Therefore, to avoid duplicate notifications, we only register a no-op.
    176         mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
    177 
    178         // TODO: Change the timeout from 0 (no onUnavailable callback) to some
    179         // moderate callback timeout. This might be useful for updating some UI.
    180         // Additionally, we log a message to aid in any subsequent debugging.
    181         mLog.i("requesting mobile upstream network: " + mobileUpstreamRequest);
    182 
    183         cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback, 0, legacyType, mHandler);
    184     }
    185 
    186     public void releaseMobileNetworkRequest() {
    187         if (mMobileNetworkCallback == null) return;
    188 
    189         cm().unregisterNetworkCallback(mMobileNetworkCallback);
    190         mMobileNetworkCallback = null;
    191     }
    192 
    193     // So many TODOs here, but chief among them is: make this functionality an
    194     // integral part of this class such that whenever a higher priority network
    195     // becomes available and useful we (a) file a request to keep it up as
    196     // necessary and (b) change all upstream tracking state accordingly (by
    197     // passing LinkProperties up to Tethering).
    198     //
    199     // Next TODO: return NetworkState instead of just the type.
    200     public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) {
    201         final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
    202                 mNetworkMap.values(), preferredTypes);
    203 
    204         mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));
    205 
    206         switch (typeStatePair.type) {
    207             case TYPE_MOBILE_DUN:
    208             case TYPE_MOBILE_HIPRI:
    209                 // If we're on DUN, put our own grab on it.
    210                 registerMobileNetworkRequest();
    211                 break;
    212             case TYPE_NONE:
    213                 break;
    214             default:
    215                 /* If we've found an active upstream connection that's not DUN/HIPRI
    216                  * we should stop any outstanding DUN/HIPRI requests.
    217                  *
    218                  * If we found NONE we don't want to do this as we want any previous
    219                  * requests to keep trying to bring up something we can use.
    220                  */
    221                 releaseMobileNetworkRequest();
    222                 break;
    223         }
    224 
    225         return typeStatePair.ns;
    226     }
    227 
    228     public void setCurrentUpstream(Network upstream) {
    229         mTetheringUpstreamNetwork = upstream;
    230     }
    231 
    232     public Set<IpPrefix> getLocalPrefixes() {
    233         return (Set<IpPrefix>) mLocalPrefixes.clone();
    234     }
    235 
    236     private void handleAvailable(int callbackType, Network network) {
    237         if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
    238 
    239         if (!mNetworkMap.containsKey(network)) {
    240             mNetworkMap.put(network,
    241                     new NetworkState(null, null, null, network, null, null));
    242         }
    243 
    244         // Always request whatever extra information we can, in case this
    245         // was already up when start() was called, in which case we would
    246         // not have been notified of any information that had not changed.
    247         switch (callbackType) {
    248             case CALLBACK_LISTEN_ALL:
    249                 break;
    250 
    251             case CALLBACK_TRACK_DEFAULT:
    252                 if (mDefaultNetworkCallback == null) {
    253                     // The callback was unregistered in the interval between
    254                     // ConnectivityService enqueueing onAvailable() and our
    255                     // handling of it here on the mHandler thread.
    256                     //
    257                     // Clean-up of this network entry is deferred to the
    258                     // handling of onLost() by other callbacks.
    259                     //
    260                     // These request*() calls can be deleted post oag/339444.
    261                     return;
    262                 }
    263                 mDefaultInternetNetwork = network;
    264                 break;
    265 
    266             case CALLBACK_MOBILE_REQUEST:
    267                 if (mMobileNetworkCallback == null) {
    268                     // The callback was unregistered in the interval between
    269                     // ConnectivityService enqueueing onAvailable() and our
    270                     // handling of it here on the mHandler thread.
    271                     //
    272                     // Clean-up of this network entry is deferred to the
    273                     // handling of onLost() by other callbacks.
    274                     return;
    275                 }
    276                 break;
    277         }
    278 
    279         // Requesting updates for mListenAllCallback is not currently possible
    280         // because it's a "listen". Two possible solutions to getting updates
    281         // about networks without waiting for a change (which might never come)
    282         // are:
    283         //
    284         //     [1] extend request{NetworkCapabilities,LinkProperties}() to
    285         //         take a Network argument and have ConnectivityService do
    286         //         what's required (if the network satisfies the request)
    287         //
    288         //     [2] explicitly file a NetworkRequest for each connectivity type
    289         //         listed as a preferred upstream and wait for these callbacks
    290         //         to be notified (requires tracking many more callbacks).
    291         //
    292         // Until this is addressed, networks that exist prior to the "listen"
    293         // registration and which do not subsequently change will not cause
    294         // us to learn their NetworkCapabilities nor their LinkProperties.
    295 
    296         // TODO: If sufficient information is available to select a more
    297         // preferable upstream, do so now and notify the target.
    298         notifyTarget(EVENT_ON_AVAILABLE, network);
    299     }
    300 
    301     private void handleNetCap(Network network, NetworkCapabilities newNc) {
    302         final NetworkState prev = mNetworkMap.get(network);
    303         if (prev == null || newNc.equals(prev.networkCapabilities)) {
    304             // Ignore notifications about networks for which we have not yet
    305             // received onAvailable() (should never happen) and any duplicate
    306             // notifications (e.g. matching more than one of our callbacks).
    307             return;
    308         }
    309 
    310         if (VDBG) {
    311             Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
    312                     network, newNc));
    313         }
    314 
    315         // Log changes in upstream network signal strength, if available.
    316         if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) {
    317             final int newSignal = newNc.getSignalStrength();
    318             final String prevSignal = getSignalStrength(prev.networkCapabilities);
    319             mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
    320         }
    321 
    322         mNetworkMap.put(network, new NetworkState(
    323                 null, prev.linkProperties, newNc, network, null, null));
    324         // TODO: If sufficient information is available to select a more
    325         // preferable upstream, do so now and notify the target.
    326         notifyTarget(EVENT_ON_CAPABILITIES, network);
    327     }
    328 
    329     private void handleLinkProp(Network network, LinkProperties newLp) {
    330         final NetworkState prev = mNetworkMap.get(network);
    331         if (prev == null || newLp.equals(prev.linkProperties)) {
    332             // Ignore notifications about networks for which we have not yet
    333             // received onAvailable() (should never happen) and any duplicate
    334             // notifications (e.g. matching more than one of our callbacks).
    335             return;
    336         }
    337 
    338         if (VDBG) {
    339             Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
    340                     network, newLp));
    341         }
    342 
    343         mNetworkMap.put(network, new NetworkState(
    344                 null, newLp, prev.networkCapabilities, network, null, null));
    345         // TODO: If sufficient information is available to select a more
    346         // preferable upstream, do so now and notify the target.
    347         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
    348     }
    349 
    350     private void handleSuspended(int callbackType, Network network) {
    351         if (callbackType != CALLBACK_LISTEN_ALL) return;
    352         if (!network.equals(mTetheringUpstreamNetwork)) return;
    353         mLog.log("SUSPENDED current upstream: " + network);
    354     }
    355 
    356     private void handleResumed(int callbackType, Network network) {
    357         if (callbackType != CALLBACK_LISTEN_ALL) return;
    358         if (!network.equals(mTetheringUpstreamNetwork)) return;
    359         mLog.log("RESUMED current upstream: " + network);
    360     }
    361 
    362     private void handleLost(int callbackType, Network network) {
    363         if (callbackType == CALLBACK_TRACK_DEFAULT) {
    364             mDefaultInternetNetwork = null;
    365             // Receiving onLost() for a default network does not necessarily
    366             // mean the network is gone.  We wait for a separate notification
    367             // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
    368             // clearing all state.
    369             return;
    370         }
    371 
    372         if (!mNetworkMap.containsKey(network)) {
    373             // Ignore loss of networks about which we had not previously
    374             // learned any information or for which we have already processed
    375             // an onLost() notification.
    376             return;
    377         }
    378 
    379         if (VDBG) Log.d(TAG, "EVENT_ON_LOST for " + network);
    380 
    381         // TODO: If sufficient information is available to select a more
    382         // preferable upstream, do so now and notify the target.  Likewise,
    383         // if the current upstream network is gone, notify the target of the
    384         // fact that we now have no upstream at all.
    385         notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
    386     }
    387 
    388     private void recomputeLocalPrefixes() {
    389         final HashSet<IpPrefix> localPrefixes = allLocalPrefixes(mNetworkMap.values());
    390         if (!mLocalPrefixes.equals(localPrefixes)) {
    391             mLocalPrefixes = localPrefixes;
    392             notifyTarget(NOTIFY_LOCAL_PREFIXES, localPrefixes.clone());
    393         }
    394     }
    395 
    396     // Fetch (and cache) a ConnectivityManager only if and when we need one.
    397     private ConnectivityManager cm() {
    398         if (mCM == null) {
    399             // MUST call the String variant to be able to write unittests.
    400             mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    401         }
    402         return mCM;
    403     }
    404 
    405     /**
    406      * A NetworkCallback class that handles information of interest directly
    407      * in the thread on which it is invoked. To avoid locking, this MUST be
    408      * run on the same thread as the target state machine's handler.
    409      */
    410     private class UpstreamNetworkCallback extends NetworkCallback {
    411         private final int mCallbackType;
    412 
    413         UpstreamNetworkCallback(int callbackType) {
    414             mCallbackType = callbackType;
    415         }
    416 
    417         @Override
    418         public void onAvailable(Network network) {
    419             handleAvailable(mCallbackType, network);
    420         }
    421 
    422         @Override
    423         public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
    424             handleNetCap(network, newNc);
    425         }
    426 
    427         @Override
    428         public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
    429             handleLinkProp(network, newLp);
    430             recomputeLocalPrefixes();
    431         }
    432 
    433         @Override
    434         public void onNetworkSuspended(Network network) {
    435             handleSuspended(mCallbackType, network);
    436         }
    437 
    438         @Override
    439         public void onNetworkResumed(Network network) {
    440             handleResumed(mCallbackType, network);
    441         }
    442 
    443         @Override
    444         public void onLost(Network network) {
    445             handleLost(mCallbackType, network);
    446             recomputeLocalPrefixes();
    447         }
    448     }
    449 
    450     private void releaseCallback(NetworkCallback cb) {
    451         if (cb != null) cm().unregisterNetworkCallback(cb);
    452     }
    453 
    454     private void notifyTarget(int which, Network network) {
    455         notifyTarget(which, mNetworkMap.get(network));
    456     }
    457 
    458     private void notifyTarget(int which, Object obj) {
    459         mTarget.sendMessage(mWhat, which, 0, obj);
    460     }
    461 
    462     private static class TypeStatePair {
    463         public int type = TYPE_NONE;
    464         public NetworkState ns = null;
    465     }
    466 
    467     private static TypeStatePair findFirstAvailableUpstreamByType(
    468             Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) {
    469         final TypeStatePair result = new TypeStatePair();
    470 
    471         for (int type : preferredTypes) {
    472             NetworkCapabilities nc;
    473             try {
    474                 nc = ConnectivityManager.networkCapabilitiesForType(type);
    475             } catch (IllegalArgumentException iae) {
    476                 Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " +
    477                        ConnectivityManager.getNetworkTypeName(type));
    478                 continue;
    479             }
    480             nc.setSingleUid(Process.myUid());
    481 
    482             for (NetworkState value : netStates) {
    483                 if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) {
    484                     continue;
    485                 }
    486 
    487                 result.type = type;
    488                 result.ns = value;
    489                 return result;
    490             }
    491         }
    492 
    493         return result;
    494     }
    495 
    496     private static HashSet<IpPrefix> allLocalPrefixes(Iterable<NetworkState> netStates) {
    497         final HashSet<IpPrefix> prefixSet = new HashSet<>();
    498 
    499         for (NetworkState ns : netStates) {
    500             final LinkProperties lp = ns.linkProperties;
    501             if (lp == null) continue;
    502             prefixSet.addAll(PrefixUtils.localPrefixesFrom(lp));
    503         }
    504 
    505         return prefixSet;
    506     }
    507 
    508     private static String getSignalStrength(NetworkCapabilities nc) {
    509         if (nc == null || !nc.hasSignalStrength()) return "unknown";
    510         return Integer.toString(nc.getSignalStrength());
    511     }
    512 }
    513