diff options
author | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-02-21 14:02:06 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-02-21 14:02:06 +0000 |
commit | 7c124b1917ee54e07ed6545164f7c881b775b242 (patch) | |
tree | 54cd6930d53251580512245c180c145c0bee39a1 | |
parent | 9da6624d38326019ba4bb9d59fc1d1e8e7c895ad (diff) | |
parent | 2c1b6d7abc5e0295ee328e4430ac4bdaf86a1e0b (diff) |
Add ModuleNetworkStackClient am: 40525479f6 am: 25fe31f1fc am: 2c1b6d7abc
Change-Id: I5bd85e68635f3a6ad6c32e805f3ef1c4bd2e24e3
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 |