1 /* 2 * Copyright (C) 2021 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.networkstack.tethering; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 21 import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK; 22 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 23 24 import static com.android.networkstack.apishim.common.ShimUtils.isAtLeastS; 25 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.fail; 28 29 import android.content.Context; 30 import android.content.Intent; 31 import android.net.ConnectivityManager; 32 import android.net.IConnectivityManager; 33 import android.net.LinkProperties; 34 import android.net.Network; 35 import android.net.NetworkCapabilities; 36 import android.net.NetworkInfo; 37 import android.net.NetworkRequest; 38 import android.os.Handler; 39 import android.os.UserHandle; 40 import android.util.ArrayMap; 41 42 import androidx.annotation.NonNull; 43 import androidx.annotation.Nullable; 44 45 import com.android.modules.utils.build.SdkLevel; 46 47 import java.util.Map; 48 import java.util.Objects; 49 50 /** 51 * Simulates upstream switching and sending NetworkCallbacks and CONNECTIVITY_ACTION broadcasts. 52 * 53 * Unlike any real networking code, this class is single-threaded and entirely synchronous. 54 * The effects of all method calls (including sending fake broadcasts, sending callbacks, etc.) are 55 * performed immediately on the caller's thread before returning. 56 * 57 * TODO: this duplicates a fair amount of code from ConnectivityManager and ConnectivityService. 58 * Consider using a ConnectivityService object instead, as used in ConnectivityServiceTest. 59 * 60 * Things to consider: 61 * - ConnectivityService uses a real handler for realism, and these test use TestLooper (or even 62 * invoke callbacks directly inline) for determinism. Using a real ConnectivityService would 63 * require adding dispatchAll() calls and migrating to handlers. 64 * - ConnectivityService does not provide a way to order CONNECTIVITY_ACTION before or after the 65 * NetworkCallbacks for the same network change. That ability is useful because the upstream 66 * selection code in Tethering is vulnerable to race conditions, due to its reliance on multiple 67 * separate NetworkCallbacks and BroadcastReceivers, each of which trigger different types of 68 * updates. If/when the upstream selection code is refactored to a more level-triggered model 69 * (e.g., with an idempotent function that takes into account all state every time any part of 70 * that state changes), this may become less important or unnecessary. 71 */ 72 public class TestConnectivityManager extends ConnectivityManager { 73 public static final boolean BROADCAST_FIRST = false; 74 public static final boolean CALLBACKS_FIRST = true; 75 76 final Map<NetworkCallback, Handler> mAllCallbacks = new ArrayMap<>(); 77 // This contains the callbacks tracking the system default network, whether it's registered 78 // with registerSystemDefaultNetworkCallback (S+) or with a custom request (R-). 79 final Map<NetworkCallback, Handler> mTrackingDefault = new ArrayMap<>(); 80 final Map<NetworkCallback, NetworkRequestInfo> mListening = new ArrayMap<>(); 81 final Map<NetworkCallback, NetworkRequestInfo> mRequested = new ArrayMap<>(); 82 final Map<NetworkCallback, Integer> mLegacyTypeMap = new ArrayMap<>(); 83 84 private final Context mContext; 85 86 private int mNetworkId = 100; 87 private TestNetworkAgent mDefaultNetwork = null; 88 89 /** 90 * Constructs a TestConnectivityManager. 91 * @param ctx the context to use. Must be a fake or a mock because otherwise the test will 92 * attempt to send real broadcasts and resulting in permission denials. 93 * @param svc an IConnectivityManager. Should be a fake or a mock. 94 */ TestConnectivityManager(Context ctx, IConnectivityManager svc)95 public TestConnectivityManager(Context ctx, IConnectivityManager svc) { 96 super(ctx, svc); 97 mContext = ctx; 98 } 99 100 static class NetworkRequestInfo { 101 public final NetworkRequest request; 102 public final Handler handler; NetworkRequestInfo(NetworkRequest r, Handler h)103 NetworkRequestInfo(NetworkRequest r, Handler h) { 104 request = r; 105 handler = h; 106 } 107 } 108 hasNoCallbacks()109 boolean hasNoCallbacks() { 110 return mAllCallbacks.isEmpty() 111 && mTrackingDefault.isEmpty() 112 && mListening.isEmpty() 113 && mRequested.isEmpty() 114 && mLegacyTypeMap.isEmpty(); 115 } 116 onlyHasDefaultCallbacks()117 boolean onlyHasDefaultCallbacks() { 118 return (mAllCallbacks.size() == 1) 119 && (mTrackingDefault.size() == 1) 120 && mListening.isEmpty() 121 && mRequested.isEmpty() 122 && mLegacyTypeMap.isEmpty(); 123 } 124 isListeningForUpstream()125 boolean isListeningForUpstream() { 126 final NetworkCapabilities upstreamNc = new NetworkCapabilities(); 127 upstreamNc.clearAll(); 128 if (SdkLevel.isAtLeastV()) { 129 upstreamNc.addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK); 130 } 131 132 for (NetworkRequestInfo nri : mListening.values()) { 133 if (nri.request.networkCapabilities.equalRequestableCapabilities(upstreamNc)) { 134 return true; 135 } 136 } 137 return false; 138 } 139 getNetworkId()140 int getNetworkId() { 141 return ++mNetworkId; 142 } 143 sendDefaultNetworkBroadcasts(TestNetworkAgent formerDefault, TestNetworkAgent defaultNetwork)144 private void sendDefaultNetworkBroadcasts(TestNetworkAgent formerDefault, 145 TestNetworkAgent defaultNetwork) { 146 if (formerDefault != null) { 147 sendConnectivityAction(formerDefault.legacyType, false /* connected */); 148 } 149 if (defaultNetwork != null) { 150 sendConnectivityAction(defaultNetwork.legacyType, true /* connected */); 151 } 152 } 153 sendDefaultNetworkCallbacks(TestNetworkAgent formerDefault, TestNetworkAgent defaultNetwork)154 private void sendDefaultNetworkCallbacks(TestNetworkAgent formerDefault, 155 TestNetworkAgent defaultNetwork) { 156 for (NetworkCallback cb : mTrackingDefault.keySet()) { 157 final Handler handler = mTrackingDefault.get(cb); 158 if (defaultNetwork != null) { 159 handler.post(() -> cb.onAvailable(defaultNetwork.networkId)); 160 handler.post(() -> cb.onCapabilitiesChanged( 161 defaultNetwork.networkId, defaultNetwork.networkCapabilities)); 162 handler.post(() -> cb.onLinkPropertiesChanged( 163 defaultNetwork.networkId, defaultNetwork.linkProperties)); 164 } else if (formerDefault != null) { 165 handler.post(() -> cb.onLost(formerDefault.networkId)); 166 } 167 } 168 } 169 makeDefaultNetwork(TestNetworkAgent agent, boolean order, @Nullable Runnable inBetween)170 void makeDefaultNetwork(TestNetworkAgent agent, boolean order, @Nullable Runnable inBetween) { 171 if (Objects.equals(mDefaultNetwork, agent)) return; 172 173 final TestNetworkAgent formerDefault = mDefaultNetwork; 174 mDefaultNetwork = agent; 175 176 if (order == CALLBACKS_FIRST) { 177 sendDefaultNetworkCallbacks(formerDefault, mDefaultNetwork); 178 if (inBetween != null) inBetween.run(); 179 sendDefaultNetworkBroadcasts(formerDefault, mDefaultNetwork); 180 } else { 181 sendDefaultNetworkBroadcasts(formerDefault, mDefaultNetwork); 182 if (inBetween != null) inBetween.run(); 183 sendDefaultNetworkCallbacks(formerDefault, mDefaultNetwork); 184 } 185 } 186 makeDefaultNetwork(TestNetworkAgent agent, boolean order)187 void makeDefaultNetwork(TestNetworkAgent agent, boolean order) { 188 makeDefaultNetwork(agent, order, null /* inBetween */); 189 } 190 makeDefaultNetwork(TestNetworkAgent agent)191 void makeDefaultNetwork(TestNetworkAgent agent) { 192 makeDefaultNetwork(agent, BROADCAST_FIRST, null /* inBetween */); 193 } 194 sendLinkProperties(TestNetworkAgent agent, boolean updateDefaultFirst)195 void sendLinkProperties(TestNetworkAgent agent, boolean updateDefaultFirst) { 196 if (!updateDefaultFirst) agent.sendLinkProperties(); 197 198 for (NetworkCallback cb : mTrackingDefault.keySet()) { 199 cb.onLinkPropertiesChanged(agent.networkId, agent.linkProperties); 200 } 201 202 if (updateDefaultFirst) agent.sendLinkProperties(); 203 } 204 looksLikeDefaultRequest(NetworkRequest req)205 static boolean looksLikeDefaultRequest(NetworkRequest req) { 206 return req.hasCapability(NET_CAPABILITY_INTERNET) 207 && !req.hasCapability(NET_CAPABILITY_DUN) 208 && !req.hasTransport(TRANSPORT_CELLULAR); 209 } 210 211 @Override requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h)212 public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) { 213 // For R- devices, Tethering will invoke this function in 2 cases, one is to request mobile 214 // network, the other is to track system default network. 215 if (looksLikeDefaultRequest(req)) { 216 assertFalse(isAtLeastS()); 217 addTrackDefaultCallback(cb, h); 218 } else { 219 assertFalse(mAllCallbacks.containsKey(cb)); 220 mAllCallbacks.put(cb, h); 221 assertFalse(mRequested.containsKey(cb)); 222 mRequested.put(cb, new NetworkRequestInfo(req, h)); 223 } 224 } 225 226 @Override registerSystemDefaultNetworkCallback( @onNull NetworkCallback cb, @NonNull Handler h)227 public void registerSystemDefaultNetworkCallback( 228 @NonNull NetworkCallback cb, @NonNull Handler h) { 229 addTrackDefaultCallback(cb, h); 230 } 231 addTrackDefaultCallback(@onNull NetworkCallback cb, @NonNull Handler h)232 private void addTrackDefaultCallback(@NonNull NetworkCallback cb, @NonNull Handler h) { 233 assertFalse(mAllCallbacks.containsKey(cb)); 234 mAllCallbacks.put(cb, h); 235 assertFalse(mTrackingDefault.containsKey(cb)); 236 mTrackingDefault.put(cb, h); 237 } 238 239 @Override requestNetwork(NetworkRequest req, NetworkCallback cb)240 public void requestNetwork(NetworkRequest req, NetworkCallback cb) { 241 fail("Should never be called."); 242 } 243 244 @Override requestNetwork(NetworkRequest req, int timeoutMs, int legacyType, Handler h, NetworkCallback cb)245 public void requestNetwork(NetworkRequest req, 246 int timeoutMs, int legacyType, Handler h, NetworkCallback cb) { 247 assertFalse(mAllCallbacks.containsKey(cb)); 248 NetworkRequest newReq = new NetworkRequest(req.networkCapabilities, legacyType, 249 -1 /** testId */, req.type); 250 mAllCallbacks.put(cb, h); 251 assertFalse(mRequested.containsKey(cb)); 252 mRequested.put(cb, new NetworkRequestInfo(newReq, h)); 253 assertFalse(mLegacyTypeMap.containsKey(cb)); 254 if (legacyType != ConnectivityManager.TYPE_NONE) { 255 mLegacyTypeMap.put(cb, legacyType); 256 } 257 } 258 259 @Override registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h)260 public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) { 261 assertFalse(mAllCallbacks.containsKey(cb)); 262 mAllCallbacks.put(cb, h); 263 assertFalse(mListening.containsKey(cb)); 264 mListening.put(cb, new NetworkRequestInfo(req, h)); 265 } 266 267 @Override registerNetworkCallback(NetworkRequest req, NetworkCallback cb)268 public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) { 269 fail("Should never be called."); 270 } 271 272 @Override registerDefaultNetworkCallback(NetworkCallback cb, Handler h)273 public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) { 274 fail("Should never be called."); 275 } 276 277 @Override registerDefaultNetworkCallback(NetworkCallback cb)278 public void registerDefaultNetworkCallback(NetworkCallback cb) { 279 fail("Should never be called."); 280 } 281 282 @Override unregisterNetworkCallback(NetworkCallback cb)283 public void unregisterNetworkCallback(NetworkCallback cb) { 284 if (mTrackingDefault.containsKey(cb)) { 285 mTrackingDefault.remove(cb); 286 } else if (mListening.containsKey(cb)) { 287 mListening.remove(cb); 288 } else if (mRequested.containsKey(cb)) { 289 mRequested.remove(cb); 290 mLegacyTypeMap.remove(cb); 291 } else { 292 fail("Unexpected callback removed"); 293 } 294 mAllCallbacks.remove(cb); 295 296 assertFalse(mAllCallbacks.containsKey(cb)); 297 assertFalse(mTrackingDefault.containsKey(cb)); 298 assertFalse(mListening.containsKey(cb)); 299 assertFalse(mRequested.containsKey(cb)); 300 } 301 sendConnectivityAction(int type, boolean connected)302 private void sendConnectivityAction(int type, boolean connected) { 303 NetworkInfo ni = new NetworkInfo(type, 0 /* subtype */, getNetworkTypeName(type), 304 "" /* subtypeName */); 305 NetworkInfo.DetailedState state = connected 306 ? NetworkInfo.DetailedState.CONNECTED 307 : NetworkInfo.DetailedState.DISCONNECTED; 308 ni.setDetailedState(state, "" /* reason */, "" /* extraInfo */); 309 Intent intent = new Intent(CONNECTIVITY_ACTION); 310 intent.putExtra(EXTRA_NETWORK_INFO, ni); 311 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 312 } 313 314 public static class TestNetworkAgent { 315 public final TestConnectivityManager cm; 316 public final Network networkId; 317 public final NetworkCapabilities networkCapabilities; 318 public final LinkProperties linkProperties; 319 // TODO: delete when tethering no longer uses CONNECTIVITY_ACTION. 320 public final int legacyType; 321 TestNetworkAgent(TestConnectivityManager cm, NetworkCapabilities nc)322 public TestNetworkAgent(TestConnectivityManager cm, NetworkCapabilities nc) { 323 this.cm = cm; 324 this.networkId = new Network(cm.getNetworkId()); 325 networkCapabilities = copy(nc); 326 linkProperties = new LinkProperties(); 327 legacyType = toLegacyType(nc); 328 } 329 TestNetworkAgent(TestConnectivityManager cm, UpstreamNetworkState state)330 public TestNetworkAgent(TestConnectivityManager cm, UpstreamNetworkState state) { 331 this.cm = cm; 332 networkId = state.network; 333 networkCapabilities = state.networkCapabilities; 334 linkProperties = state.linkProperties; 335 this.legacyType = toLegacyType(networkCapabilities); 336 } 337 toLegacyType(NetworkCapabilities nc)338 private static int toLegacyType(NetworkCapabilities nc) { 339 for (int type = 0; type < ConnectivityManager.TYPE_TEST; type++) { 340 if (matchesLegacyType(nc, type)) return type; 341 } 342 throw new IllegalArgumentException(("Can't determine legacy type for: ") + nc); 343 } 344 matchesLegacyType(NetworkCapabilities nc, int legacyType)345 private static boolean matchesLegacyType(NetworkCapabilities nc, int legacyType) { 346 final NetworkCapabilities typeNc; 347 try { 348 typeNc = ConnectivityManager.networkCapabilitiesForType(legacyType); 349 } catch (IllegalArgumentException e) { 350 // networkCapabilitiesForType does not support all legacy types. 351 return false; 352 } 353 return typeNc.satisfiedByNetworkCapabilities(nc); 354 } 355 matchesLegacyType(int legacyType)356 private boolean matchesLegacyType(int legacyType) { 357 return matchesLegacyType(networkCapabilities, legacyType); 358 } 359 maybeSendConnectivityBroadcast(boolean connected)360 private void maybeSendConnectivityBroadcast(boolean connected) { 361 for (Integer requestedLegacyType : cm.mLegacyTypeMap.values()) { 362 if (requestedLegacyType.intValue() == legacyType) { 363 cm.sendConnectivityAction(legacyType, connected /* connected */); 364 // In practice, a given network can match only one legacy type. 365 break; 366 } 367 } 368 } 369 fakeConnect()370 public void fakeConnect() { 371 fakeConnect(BROADCAST_FIRST, null); 372 } 373 fakeConnect(boolean order, @Nullable Runnable inBetween)374 public void fakeConnect(boolean order, @Nullable Runnable inBetween) { 375 if (order == BROADCAST_FIRST) { 376 maybeSendConnectivityBroadcast(true /* connected */); 377 if (inBetween != null) inBetween.run(); 378 } 379 380 for (NetworkCallback cb : cm.mListening.keySet()) { 381 final NetworkRequestInfo nri = cm.mListening.get(cb); 382 nri.handler.post(() -> cb.onAvailable(networkId)); 383 nri.handler.post(() -> cb.onCapabilitiesChanged( 384 networkId, copy(networkCapabilities))); 385 nri.handler.post(() -> cb.onLinkPropertiesChanged(networkId, copy(linkProperties))); 386 } 387 388 if (order == CALLBACKS_FIRST) { 389 if (inBetween != null) inBetween.run(); 390 maybeSendConnectivityBroadcast(true /* connected */); 391 } 392 // mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork 393 } 394 fakeDisconnect()395 public void fakeDisconnect() { 396 fakeDisconnect(BROADCAST_FIRST, null); 397 } 398 fakeDisconnect(boolean order, @Nullable Runnable inBetween)399 public void fakeDisconnect(boolean order, @Nullable Runnable inBetween) { 400 if (order == BROADCAST_FIRST) { 401 maybeSendConnectivityBroadcast(false /* connected */); 402 if (inBetween != null) inBetween.run(); 403 } 404 405 for (NetworkCallback cb : cm.mListening.keySet()) { 406 cb.onLost(networkId); 407 } 408 409 if (order == CALLBACKS_FIRST) { 410 if (inBetween != null) inBetween.run(); 411 maybeSendConnectivityBroadcast(false /* connected */); 412 } 413 // mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork 414 } 415 sendLinkProperties()416 public void sendLinkProperties() { 417 for (NetworkCallback cb : cm.mListening.keySet()) { 418 cb.onLinkPropertiesChanged(networkId, copy(linkProperties)); 419 } 420 } 421 422 @Override toString()423 public String toString() { 424 return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities); 425 } 426 } 427 copy(NetworkCapabilities nc)428 static NetworkCapabilities copy(NetworkCapabilities nc) { 429 return new NetworkCapabilities(nc); 430 } 431 copy(LinkProperties lp)432 static LinkProperties copy(LinkProperties lp) { 433 return new LinkProperties(lp); 434 } 435 } 436