diff options
author | Remi NGUYEN VAN <reminv@google.com> | 2020-01-07 09:55:38 +0900 |
---|---|---|
committer | Roshan Pius <rpius@google.com> | 2020-02-20 14:58:59 -0800 |
commit | 40525479f6c286962b1bdbc056b4f9d010b45a34 (patch) | |
tree | 3bdde73ea4c097c15bf644bf54e7c35891f74765 | |
parent | e1aa745f9fd8deace324c48957c7f4ed9ac1d35c (diff) |
Add ModuleNetworkStackClient
NetworkStackClientBase / ModuleNetworkStackClient extract functionality
from NetworkStackClient in services.jar, only keeping what is needed for
modules to communicate with the NetworkStack, and not the framework code
that starts the NetworkStack (and uses hidden APIs).
As the NetworkStack is started asynchronously, it is possible for it not
to be registered yet right after boot, so some polling is implemented to
queue requests until it is ready.
NetworkStackClient will inherit from NetworkStackClientBase so that
components in services.jar can access the NetworkStack, using the same
asynchronous mechanism as before (execute pending requests once the
service is connected).
Bug: 147255753
Test: atest NetworkStackTests NetworkStackNextTests FrameworksNetTests
Change-Id: I130aabb3992280d875e0d20b16bc09a9e0261fda
5 files changed, 404 insertions, 0 deletions
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp index a34a637..16fdd56 100644 --- a/common/networkstackclient/Android.bp +++ b/common/networkstackclient/Android.bp @@ -37,6 +37,9 @@ aidl_interface { "3", "4", ], + visibility: [ + "//system/tools/aidl/build" + ], } aidl_interface { @@ -84,6 +87,11 @@ aidl_interface { "3", "4", ], + // TODO: have tethering depend on networkstack-client and set visibility to private + visibility: [ + "//system/tools/aidl/build", + "//frameworks/base/packages/Tethering" + ], } java_library { @@ -93,9 +101,16 @@ java_library { ":framework-annotations", "src/android/net/IpMemoryStoreClient.java", "src/android/net/ipmemorystore/**/*.java", + "src/android/net/networkstack/**/*.java", ], static_libs: [ "ipmemorystore-aidl-interfaces-V3-java", "networkstack-aidl-interfaces-unstable-java", ], + visibility: [ + "//frameworks/base/packages/Tethering", + "//frameworks/base/services/net", + "//frameworks/opt/net/wifi/service", + "//packages/modules/NetworkStack", + ] } diff --git a/common/networkstackclient/src/android/net/networkstack/ModuleNetworkStackClient.java b/common/networkstackclient/src/android/net/networkstack/ModuleNetworkStackClient.java new file mode 100644 index 0000000..cfbb760 --- /dev/null +++ b/common/networkstackclient/src/android/net/networkstack/ModuleNetworkStackClient.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.networkstack; + +import static android.content.Context.NETWORK_STACK_SERVICE; +import static android.os.Build.VERSION.SDK_INT; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.INetworkStackConnector; +import android.os.Build; +import android.os.IBinder; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * A {@link NetworkStackClientBase} implementation for use within modules (not the system server). + */ +public class ModuleNetworkStackClient extends NetworkStackClientBase { + private static final String TAG = ModuleNetworkStackClient.class.getSimpleName(); + + private ModuleNetworkStackClient() {} + + private static ModuleNetworkStackClient sInstance; + + /** + * Get an instance of the ModuleNetworkStackClient. + * @param packageContext Context to use to obtain the network stack connector. + */ + @NonNull + public static synchronized ModuleNetworkStackClient getInstance(Context packageContext) { + // TODO(b/149676685): change this check to "< R" once R is defined + if (SDK_INT < Build.VERSION_CODES.Q + || (SDK_INT == Build.VERSION_CODES.Q && "REL".equals(Build.VERSION.CODENAME))) { + // NETWORK_STACK_SERVICE is not available through getSystemService before R + throw new UnsupportedOperationException( + "ModuleNetworkStackClient is not supported on API " + SDK_INT); + } + + if (sInstance == null) { + sInstance = new ModuleNetworkStackClient(); + sInstance.startPolling(packageContext); + } + return sInstance; + } + + @VisibleForTesting + protected static synchronized void resetInstanceForTest() { + sInstance = null; + } + + private void startPolling(Context context) { + // If the service is already registered (as it will be most of the time), do not poll and + // fulfill requests immediately. + final IBinder nss = (IBinder) context.getSystemService(NETWORK_STACK_SERVICE); + if (nss != null) { + // Calling onNetworkStackConnected here means that pending oneway Binder calls to the + // NetworkStack get sent from the current thread and not a worker thread; this is fine + // considering that those are only non-blocking, oneway Binder calls. + onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(nss)); + return; + } + new Thread(new PollingRunner(context)).start(); + } + + private class PollingRunner implements Runnable { + private final Context mContext; + + private PollingRunner(Context context) { + mContext = context; + } + + @Override + public void run() { + // Block until the NetworkStack connector is registered in ServiceManager. + IBinder nss; + while ((nss = (IBinder) mContext.getSystemService(NETWORK_STACK_SERVICE)) == null) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for NetworkStack connector", e); + // Keep trying, the system would just crash without a connector + } + } + + onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(nss)); + } + } +} diff --git a/common/networkstackclient/src/android/net/networkstack/NetworkStackClientBase.java b/common/networkstackclient/src/android/net/networkstack/NetworkStackClientBase.java new file mode 100644 index 0000000..c2f7ddd --- /dev/null +++ b/common/networkstackclient/src/android/net/networkstack/NetworkStackClientBase.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.networkstack; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.IIpMemoryStoreCallbacks; +import android.net.INetworkMonitorCallbacks; +import android.net.INetworkStackConnector; +import android.net.Network; +import android.net.dhcp.DhcpServingParamsParcel; +import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; +import android.os.RemoteException; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * Utility class to obtain and communicate with the NetworkStack module. + */ +public abstract class NetworkStackClientBase { + @NonNull + @GuardedBy("mPendingNetStackRequests") + private final ArrayList<Consumer<INetworkStackConnector>> mPendingNetStackRequests = + new ArrayList<>(); + + @Nullable + @GuardedBy("mPendingNetStackRequests") + private INetworkStackConnector mConnector; + + /** + * Create a DHCP server according to the specified parameters. + * + * <p>The server will be returned asynchronously through the provided callbacks. + */ + public void makeDhcpServer(final String ifName, final DhcpServingParamsParcel params, + final IDhcpServerCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeDhcpServer(ifName, params, cb); + } catch (RemoteException e) { + throw new IllegalStateException("Could not create DhcpServer", e); + } + }); + } + + /** + * Create an IpClient on the specified interface. + * + * <p>The IpClient will be returned asynchronously through the provided callbacks. + */ + public void makeIpClient(String ifName, IIpClientCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeIpClient(ifName, cb); + } catch (RemoteException e) { + throw new IllegalStateException("Could not create IpClient", e); + } + }); + } + + /** + * Create a NetworkMonitor. + * + * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks. + */ + public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeNetworkMonitor(network, name, cb); + } catch (RemoteException e) { + throw new IllegalStateException("Could not create NetworkMonitor", e); + } + }); + } + + /** + * Get an instance of the IpMemoryStore. + * + * <p>The IpMemoryStore will be returned asynchronously through the provided callbacks. + */ + public void fetchIpMemoryStore(IIpMemoryStoreCallbacks cb) { + requestConnector(connector -> { + try { + connector.fetchIpMemoryStore(cb); + } catch (RemoteException e) { + throw new IllegalStateException("Could not fetch IpMemoryStore", e); + } + }); + } + + protected void requestConnector(@NonNull Consumer<INetworkStackConnector> request) { + final INetworkStackConnector connector; + synchronized (mPendingNetStackRequests) { + connector = mConnector; + if (connector == null) { + mPendingNetStackRequests.add(request); + return; + } + } + + request.accept(connector); + } + + /** + * Call this method once the NetworkStack is connected. + * + * <p>This method will cause pending oneway Binder calls for the NetworkStack to be processed on + * the calling thread. + */ + protected void onNetworkStackConnected(@NonNull INetworkStackConnector connector) { + // Process the connector wait queue in order, including any items that are added + // while processing. + while (true) { + final ArrayList<Consumer<INetworkStackConnector>> requests; + synchronized (mPendingNetStackRequests) { + requests = new ArrayList<>(mPendingNetStackRequests); + mPendingNetStackRequests.clear(); + } + + for (Consumer<INetworkStackConnector> consumer : requests) { + consumer.accept(connector); + } + + synchronized (mPendingNetStackRequests) { + if (mPendingNetStackRequests.size() == 0) { + // Once mConnector is non-null, no more tasks will be queued. + mConnector = connector; + return; + } + } + } + } + + /** + * Used in subclasses for diagnostics (dumpsys) purposes. + * @return How many requests for the network stack are currently pending. + */ + protected int getQueueLength() { + synchronized (mPendingNetStackRequests) { + return mPendingNetStackRequests.size(); + } + } +} diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 370691b..565a5cb 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp @@ -20,6 +20,7 @@ java_defaults { srcs: ["src/**/*.java", "src/**/*.kt"], resource_dirs: ["res"], static_libs: [ + "androidx.test.ext.junit", "androidx.test.rules", "kotlin-reflect", "mockito-target-extended-minus-junit4", diff --git a/tests/unit/src/android/net/networkstack/ModuleNetworkStackClientTest.kt b/tests/unit/src/android/net/networkstack/ModuleNetworkStackClientTest.kt new file mode 100644 index 0000000..661f9aa --- /dev/null +++ b/tests/unit/src/android/net/networkstack/ModuleNetworkStackClientTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.networkstack + +import android.content.Context +import android.net.IIpMemoryStoreCallbacks +import android.net.INetworkMonitorCallbacks +import android.net.INetworkStackConnector +import android.net.Network +import android.net.dhcp.DhcpServingParamsParcel +import android.net.dhcp.IDhcpServerCallbacks +import android.net.ip.IIpClientCallbacks +import android.os.Build +import android.os.IBinder +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.networkstack.apishim.ShimUtils +import org.junit.After +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.never +import org.mockito.Mockito.timeout +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidJUnit4::class) +@SmallTest +class ModuleNetworkStackClientTest { + private val TEST_IFNAME = "testiface" + private val TEST_NETWORK = Network(43) + private val TEST_TIMEOUT_MS = 500L + + @Mock + private lateinit var mContext: Context + @Mock + private lateinit var mConnectorBinder: IBinder + @Mock + private lateinit var mConnector: INetworkStackConnector + @Mock + private lateinit var mIpClientCb: IIpClientCallbacks + @Mock + private lateinit var mDhcpServerCb: IDhcpServerCallbacks + @Mock + private lateinit var mNetworkMonitorCb: INetworkMonitorCallbacks + @Mock + private lateinit var mIpMemoryStoreCb: IIpMemoryStoreCallbacks + + @Before + fun setUp() { + // ModuleNetworkStackClient is only available after Q + assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) + MockitoAnnotations.initMocks(this) + doReturn(mConnector).`when`(mConnectorBinder).queryLocalInterface( + INetworkStackConnector::class.qualifiedName!!) + } + + @After + fun tearDown() { + ModuleNetworkStackClient.resetInstanceForTest() + } + + @Test + fun testIpClientServiceAvailableImmediately() { + doReturn(mConnectorBinder).`when`(mContext).getSystemService(Context.NETWORK_STACK_SERVICE) + ModuleNetworkStackClient.getInstance(mContext).makeIpClient(TEST_IFNAME, mIpClientCb) + verify(mConnector).makeIpClient(TEST_IFNAME, mIpClientCb) + } + + @Test + fun testIpClientServiceAvailableAfterPolling() { + ModuleNetworkStackClient.getInstance(mContext).makeIpClient(TEST_IFNAME, mIpClientCb) + + Thread.sleep(TEST_TIMEOUT_MS) + verify(mConnector, never()).makeIpClient(any(), any()) + doReturn(mConnectorBinder).`when`(mContext).getSystemService(Context.NETWORK_STACK_SERVICE) + // Use a longer timeout as polling can cause larger delays + verify(mConnector, timeout(TEST_TIMEOUT_MS * 4)).makeIpClient(TEST_IFNAME, mIpClientCb) + } + + @Test + fun testDhcpServerAvailableImmediately() { + doReturn(mConnectorBinder).`when`(mContext).getSystemService(Context.NETWORK_STACK_SERVICE) + val testParams = DhcpServingParamsParcel() + ModuleNetworkStackClient.getInstance(mContext).makeDhcpServer(TEST_IFNAME, testParams, + mDhcpServerCb) + verify(mConnector).makeDhcpServer(TEST_IFNAME, testParams, mDhcpServerCb) + } + + @Test + fun testNetworkMonitorAvailableImmediately() { + doReturn(mConnectorBinder).`when`(mContext).getSystemService(Context.NETWORK_STACK_SERVICE) + val testName = "NetworkMonitorName" + ModuleNetworkStackClient.getInstance(mContext).makeNetworkMonitor(TEST_NETWORK, testName, + mNetworkMonitorCb) + verify(mConnector).makeNetworkMonitor(TEST_NETWORK, testName, mNetworkMonitorCb) + } + + @Test + fun testIpMemoryStoreAvailableImmediately() { + doReturn(mConnectorBinder).`when`(mContext).getSystemService(Context.NETWORK_STACK_SERVICE) + ModuleNetworkStackClient.getInstance(mContext).fetchIpMemoryStore(mIpMemoryStoreCb) + verify(mConnector).fetchIpMemoryStore(mIpMemoryStoreCb) + } +}
\ No newline at end of file |