diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl index 89dcd394d69143fcf5086a2946d0defe85689fa1..a9da8d65490e6df64e35c557793dca142f362c7d 100644 --- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl +++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl @@ -38,6 +38,8 @@ interface IThreadNetworkController { void scheduleMigration(in PendingOperationalDataset pendingOpDataset, in IOperationReceiver receiver); void leave(in IOperationReceiver receiver); + void setTestNetworkAsUpstream(in String testNetworkInterfaceName, in IOperationReceiver receiver); + int getThreadVersion(); void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver); } diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java index 34b0b06976545ed9a3469dc7f14ea7c1fd01b3e4..b5699a90e408ad92df2a3a22c064917f4ef490b7 100644 --- a/thread/framework/java/android/net/thread/ThreadNetworkController.java +++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java @@ -31,6 +31,7 @@ import android.os.OutcomeReceiver; import android.os.RemoteException; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -499,6 +500,31 @@ public final class ThreadNetworkController { } } + /** + * Sets to use a specified test network as the upstream. + * + * @param testNetworkInterfaceName The name of the test network interface. When it's null, + * forbids using test network as an upstream. + * @param executor the executor to execute {@code receiver} + * @param receiver the receiver to receive result of this operation + * @hide + */ + @VisibleForTesting + @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") + public void setTestNetworkAsUpstream( + @Nullable String testNetworkInterfaceName, + @NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { + requireNonNull(executor, "executor cannot be null"); + requireNonNull(receiver, "receiver cannot be null"); + try { + mControllerService.setTestNetworkAsUpstream( + testNetworkInterfaceName, new OperationReceiverProxy(executor, receiver)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private static <T> void propagateError( Executor executor, OutcomeReceiver<T, ThreadNetworkException> receiver, diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java index 60c97bfe35cdec45618ba7dcd5fe52319b18d382..1d8cd730ba59b4ed8ca74aa678acefeb80f5b00b 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java @@ -53,14 +53,15 @@ import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME; import android.Manifest.permission; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.net.ConnectivityManager; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalNetworkConfig; -import android.net.MulticastRoutingConfig; import android.net.LocalNetworkInfo; +import android.net.MulticastRoutingConfig; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkAgentConfig; @@ -69,6 +70,7 @@ import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.NetworkScore; import android.net.RouteInfo; +import android.net.TestNetworkSpecifier; import android.net.thread.ActiveOperationalDataset; import android.net.thread.ActiveOperationalDataset.SecurityPolicy; import android.net.thread.IActiveOperationalDatasetReceiver; @@ -91,12 +93,12 @@ import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.server.ServiceManagerWrapper; +import com.android.server.thread.openthread.BorderRouterConfigurationParcel; import com.android.server.thread.openthread.IOtDaemon; import com.android.server.thread.openthread.IOtDaemonCallback; import com.android.server.thread.openthread.IOtStatusReceiver; import com.android.server.thread.openthread.Ipv6AddressInfo; import com.android.server.thread.openthread.OtDaemonState; -import com.android.server.thread.openthread.BorderRouterConfigurationParcel; import java.io.IOException; import java.net.Inet6Address; @@ -147,9 +149,10 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub private MulticastRoutingConfig mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE; private MulticastRoutingConfig mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE; private Network mUpstreamNetwork; - private final NetworkRequest mUpstreamNetworkRequest; + private NetworkRequest mUpstreamNetworkRequest; + private UpstreamNetworkCallback mUpstreamNetworkCallback; + private TestNetworkSpecifier mUpstreamTestNetworkSpecifier; private final HashMap<Network, String> mNetworkToInterface; - private final LocalNetworkConfig mLocalNetworkConfig; private BorderRouterConfigurationParcel mBorderRouterConfig; @@ -168,16 +171,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub mOtDaemonSupplier = otDaemonSupplier; mConnectivityManager = connectivityManager; mTunIfController = tunIfController; - mUpstreamNetworkRequest = - new NetworkRequest.Builder() - .clearCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) - .build(); - mLocalNetworkConfig = - new LocalNetworkConfig.Builder() - .setUpstreamSelector(mUpstreamNetworkRequest) - .build(); + mUpstreamNetworkRequest = newUpstreamNetworkRequest(); mNetworkToInterface = new HashMap<Network, String>(); mBorderRouterConfig = new BorderRouterConfigurationParcel(); } @@ -237,6 +231,60 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub LinkAddress.LIFETIME_PERMANENT /* expirationTime */); } + private NetworkRequest newUpstreamNetworkRequest() { + NetworkRequest.Builder builder = new NetworkRequest.Builder().clearCapabilities(); + + if (mUpstreamTestNetworkSpecifier != null) { + return builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST) + .setNetworkSpecifier(mUpstreamTestNetworkSpecifier) + .build(); + } + return builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(); + } + + private LocalNetworkConfig newLocalNetworkConfig() { + return new LocalNetworkConfig.Builder() + .setUpstreamMulticastRoutingConfig(mUpstreamMulticastRoutingConfig) + .setDownstreamMulticastRoutingConfig(mDownstreamMulticastRoutingConfig) + .setUpstreamSelector(mUpstreamNetworkRequest) + .build(); + } + + @Override + public void setTestNetworkAsUpstream( + @Nullable String testNetworkInterfaceName, @NonNull IOperationReceiver receiver) { + enforceAllCallingPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED); + + Log.i(TAG, "setTestNetworkAsUpstream: " + testNetworkInterfaceName); + mHandler.post(() -> setTestNetworkAsUpstreamInternal(testNetworkInterfaceName, receiver)); + } + + private void setTestNetworkAsUpstreamInternal( + @Nullable String testNetworkInterfaceName, @NonNull IOperationReceiver receiver) { + checkOnHandlerThread(); + + TestNetworkSpecifier testNetworkSpecifier = null; + if (testNetworkInterfaceName != null) { + testNetworkSpecifier = new TestNetworkSpecifier(testNetworkInterfaceName); + } + + if (!Objects.equals(mUpstreamTestNetworkSpecifier, testNetworkSpecifier)) { + cancelRequestUpstreamNetwork(); + mUpstreamTestNetworkSpecifier = testNetworkSpecifier; + mUpstreamNetworkRequest = newUpstreamNetworkRequest(); + requestUpstreamNetwork(); + sendLocalNetworkConfig(); + } + try { + receiver.onSuccess(); + } catch (RemoteException ignored) { + // do nothing if the client is dead + } + } + private void initializeOtDaemon() { try { getOtDaemon(); @@ -289,45 +337,63 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub } private void requestUpstreamNetwork() { + if (mUpstreamNetworkCallback != null) { + throw new AssertionError("The upstream network request is already there."); + } + mUpstreamNetworkCallback = new UpstreamNetworkCallback(); mConnectivityManager.registerNetworkCallback( - mUpstreamNetworkRequest, - new ConnectivityManager.NetworkCallback() { - @Override - public void onAvailable(@NonNull Network network) { - Log.i(TAG, "onAvailable: " + network); - } + mUpstreamNetworkRequest, mUpstreamNetworkCallback, mHandler); + } - @Override - public void onLost(@NonNull Network network) { - Log.i(TAG, "onLost: " + network); - } + private void cancelRequestUpstreamNetwork() { + if (mUpstreamNetworkCallback == null) { + throw new AssertionError("The upstream network request null."); + } + mNetworkToInterface.clear(); + mConnectivityManager.unregisterNetworkCallback(mUpstreamNetworkCallback); + mUpstreamNetworkCallback = null; + } - @Override - public void onLinkPropertiesChanged( - @NonNull Network network, @NonNull LinkProperties linkProperties) { - Log.i( - TAG, - String.format( - "onLinkPropertiesChanged: {network: %s, interface: %s}", - network, linkProperties.getInterfaceName())); - mNetworkToInterface.put(network, linkProperties.getInterfaceName()); - if (network.equals(mUpstreamNetwork)) { - enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork)); - } - } - }, - mHandler); + private final class UpstreamNetworkCallback extends ConnectivityManager.NetworkCallback { + @Override + public void onAvailable(@NonNull Network network) { + checkOnHandlerThread(); + Log.i(TAG, "onAvailable: " + network); + } + + @Override + public void onLost(@NonNull Network network) { + checkOnHandlerThread(); + Log.i(TAG, "onLost: " + network); + } + + @Override + public void onLinkPropertiesChanged( + @NonNull Network network, @NonNull LinkProperties linkProperties) { + checkOnHandlerThread(); + Log.i( + TAG, + String.format( + "onLinkPropertiesChanged: {network: %s, interface: %s}", + network, linkProperties.getInterfaceName())); + mNetworkToInterface.put(network, linkProperties.getInterfaceName()); + if (network.equals(mUpstreamNetwork)) { + enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork)); + } + } } private final class ThreadNetworkCallback extends ConnectivityManager.NetworkCallback { @Override public void onAvailable(@NonNull Network network) { + checkOnHandlerThread(); Log.i(TAG, "onAvailable: Thread network Available"); } @Override public void onLocalNetworkInfoChanged( @NonNull Network network, @NonNull LocalNetworkInfo localNetworkInfo) { + checkOnHandlerThread(); Log.i(TAG, "onLocalNetworkInfoChanged: " + localNetworkInfo); if (localNetworkInfo.getUpstreamNetwork() == null) { mUpstreamNetwork = null; @@ -370,7 +436,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub TAG, netCaps, mLinkProperties, - mLocalNetworkConfig, + newLocalNetworkConfig(), score, new NetworkAgentConfig.Builder().build(), mNetworkProvider) {}; @@ -754,20 +820,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub if (mNetworkAgent == null) { return; } - final LocalNetworkConfig.Builder configBuilder = new LocalNetworkConfig.Builder(); - LocalNetworkConfig localNetworkConfig = - configBuilder - .setUpstreamMulticastRoutingConfig(mUpstreamMulticastRoutingConfig) - .setDownstreamMulticastRoutingConfig(mDownstreamMulticastRoutingConfig) - .setUpstreamSelector(mUpstreamNetworkRequest) - .build(); + final LocalNetworkConfig localNetworkConfig = newLocalNetworkConfig(); mNetworkAgent.sendLocalNetworkConfig(localNetworkConfig); - Log.d( - TAG, - "Sent localNetworkConfig with upstreamConfig " - + mUpstreamMulticastRoutingConfig - + " downstreamConfig" - + mDownstreamMulticastRoutingConfig); + Log.d(TAG, "Sent localNetworkConfig: " + localNetworkConfig); } private void handleMulticastForwardingStateChanged(boolean isEnabled) { @@ -800,8 +855,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub MulticastRoutingConfig newDownstreamConfig; MulticastRoutingConfig.Builder builder; - if (mDownstreamMulticastRoutingConfig.getForwardingMode() != - MulticastRoutingConfig.FORWARD_SELECTED) { + if (mDownstreamMulticastRoutingConfig.getForwardingMode() + != MulticastRoutingConfig.FORWARD_SELECTED) { Log.e( TAG, "Ignore multicast listening address updates when downstream multicast " @@ -809,8 +864,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub // Don't update the address set if downstream multicast forwarding is disabled. return; } - if (isAdded == - mDownstreamMulticastRoutingConfig.getListeningAddresses().contains(address)) { + if (isAdded + == mDownstreamMulticastRoutingConfig.getListeningAddresses().contains(address)) { return; } diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java index 2f120b2475a2bbae76a99122c28604389ea36844..75eb043180a4a7eadfeaf1cbf4d3b2e5b71efd24 100644 --- a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java +++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java @@ -28,11 +28,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; -import android.net.thread.IActiveOperationalDatasetReceiver; -import android.net.thread.IOperationReceiver; -import android.net.thread.IOperationalDatasetCallback; -import android.net.thread.IStateCallback; -import android.net.thread.IThreadNetworkController; import android.net.thread.ThreadNetworkController.OperationalDatasetCallback; import android.net.thread.ThreadNetworkController.StateCallback; import android.os.Binder; @@ -111,6 +106,11 @@ public final class ThreadNetworkControllerTest { return (IOperationReceiver) invocation.getArguments()[1]; } + private static IOperationReceiver getSetTestNetworkAsUpstreamReceiver( + InvocationOnMock invocation) { + return (IOperationReceiver) invocation.getArguments()[1]; + } + private static IActiveOperationalDatasetReceiver getCreateDatasetReceiver( InvocationOnMock invocation) { return (IActiveOperationalDatasetReceiver) invocation.getArguments()[1]; @@ -359,4 +359,27 @@ public final class ThreadNetworkControllerTest { assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID); assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid()); } + + @Test + public void setTestNetworkAsUpstream_callbackIsInvokedWithCallingAppIdentity() + throws Exception { + setBinderUid(SYSTEM_UID); + + AtomicInteger callbackUid = new AtomicInteger(0); + + doAnswer( + invoke -> { + getSetTestNetworkAsUpstreamReceiver(invoke).onSuccess(); + return null; + }) + .when(mMockService) + .setTestNetworkAsUpstream(anyString(), any(IOperationReceiver.class)); + mController.setTestNetworkAsUpstream( + null, Runnable::run, v -> callbackUid.set(Binder.getCallingUid())); + mController.setTestNetworkAsUpstream( + new String("test0"), Runnable::run, v -> callbackUid.set(Binder.getCallingUid())); + + assertThat(callbackUid.get()).isNotEqualTo(SYSTEM_UID); + assertThat(callbackUid.get()).isEqualTo(Process.myUid()); + } }