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
17package com.android.server.connectivity.tethering;
18
19import static android.net.ConnectivityManager.getNetworkTypeName;
20import static android.net.ConnectivityManager.TYPE_NONE;
21import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
22import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
23
24import android.content.Context;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.Process;
28import android.net.ConnectivityManager;
29import android.net.ConnectivityManager.NetworkCallback;
30import android.net.IpPrefix;
31import android.net.LinkAddress;
32import android.net.LinkProperties;
33import android.net.Network;
34import android.net.NetworkCapabilities;
35import android.net.NetworkRequest;
36import android.net.NetworkState;
37import android.net.util.NetworkConstants;
38import android.net.util.PrefixUtils;
39import android.net.util.SharedLog;
40import android.util.Log;
41
42import com.android.internal.annotations.VisibleForTesting;
43import com.android.internal.util.StateMachine;
44
45import java.util.Collections;
46import java.util.HashMap;
47import java.util.HashSet;
48import 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 */
72public 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