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