• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what)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
UpstreamNetworkMonitor( ConnectivityManager cm, StateMachine tgt, SharedLog log, int what)114     public UpstreamNetworkMonitor(
115             ConnectivityManager cm, StateMachine tgt, SharedLog log, int what) {
116         this((Context) null, tgt, log, what);
117         mCM = cm;
118     }
119 
start()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 
stop()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 
updateMobileRequiresDun(boolean dunRequired)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 
mobileNetworkRequested()155     public boolean mobileNetworkRequested() {
156         return (mMobileNetworkCallback != null);
157     }
158 
registerMobileNetworkRequest()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 
releaseMobileNetworkRequest()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.
selectPreferredUpstreamType(Iterable<Integer> preferredTypes)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 
setCurrentUpstream(Network upstream)228     public void setCurrentUpstream(Network upstream) {
229         mTetheringUpstreamNetwork = upstream;
230     }
231 
getLocalPrefixes()232     public Set<IpPrefix> getLocalPrefixes() {
233         return (Set<IpPrefix>) mLocalPrefixes.clone();
234     }
235 
handleAvailable(int callbackType, Network network)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 
handleNetCap(Network network, NetworkCapabilities newNc)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 
handleLinkProp(Network network, LinkProperties newLp)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 
handleSuspended(int callbackType, Network network)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 
handleResumed(int callbackType, Network network)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 
handleLost(int callbackType, Network network)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 
recomputeLocalPrefixes()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.
cm()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 
UpstreamNetworkCallback(int callbackType)413         UpstreamNetworkCallback(int callbackType) {
414             mCallbackType = callbackType;
415         }
416 
417         @Override
onAvailable(Network network)418         public void onAvailable(Network network) {
419             handleAvailable(mCallbackType, network);
420         }
421 
422         @Override
onCapabilitiesChanged(Network network, NetworkCapabilities newNc)423         public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
424             handleNetCap(network, newNc);
425         }
426 
427         @Override
onLinkPropertiesChanged(Network network, LinkProperties newLp)428         public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
429             handleLinkProp(network, newLp);
430             recomputeLocalPrefixes();
431         }
432 
433         @Override
onNetworkSuspended(Network network)434         public void onNetworkSuspended(Network network) {
435             handleSuspended(mCallbackType, network);
436         }
437 
438         @Override
onNetworkResumed(Network network)439         public void onNetworkResumed(Network network) {
440             handleResumed(mCallbackType, network);
441         }
442 
443         @Override
onLost(Network network)444         public void onLost(Network network) {
445             handleLost(mCallbackType, network);
446             recomputeLocalPrefixes();
447         }
448     }
449 
releaseCallback(NetworkCallback cb)450     private void releaseCallback(NetworkCallback cb) {
451         if (cb != null) cm().unregisterNetworkCallback(cb);
452     }
453 
notifyTarget(int which, Network network)454     private void notifyTarget(int which, Network network) {
455         notifyTarget(which, mNetworkMap.get(network));
456     }
457 
notifyTarget(int which, Object obj)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 
findFirstAvailableUpstreamByType( Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes)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 
allLocalPrefixes(Iterable<NetworkState> netStates)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 
getSignalStrength(NetworkCapabilities nc)508     private static String getSignalStrength(NetworkCapabilities nc) {
509         if (nc == null || !nc.hasSignalStrength()) return "unknown";
510         return Integer.toString(nc.getSignalStrength());
511     }
512 }
513