Merge changes Ic597ee91,I6a2b48aa,Ia71c79aa,I570dbff5,Ia063deea, ... into main
* changes:
Get rid of volatile mIsAborted
Get rid of volatile mIsRunning
Add some more client network tests
Add test for when BluetoothSocket#connect throws
Add a test for when createInsecureL2capChannel throws
Add a test that brings up an L2cap server network
Move creating L2capIpClient to Dependencies
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index 814a068..9d070cd 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -61,6 +61,7 @@
import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.ServiceConnectivityJni;
import com.android.server.net.L2capNetwork;
+import com.android.server.net.L2capNetwork.L2capIpClient;
import com.android.server.net.L2capPacketForwarder;
import java.io.IOException;
@@ -270,7 +271,6 @@
private class AcceptThread extends Thread {
private static final int TIMEOUT_MS = 500;
private final BluetoothServerSocket mServerSocket;
- private volatile boolean mIsRunning = true;
public AcceptThread(BluetoothServerSocket serverSocket) {
super("L2capNetworkProvider-AcceptThread");
@@ -294,16 +294,17 @@
@Override
public void run() {
- while (mIsRunning) {
+ while (true) {
final BluetoothSocket connectedSocket;
try {
connectedSocket = mServerSocket.accept();
} catch (IOException e) {
- // BluetoothServerSocket was closed().
- if (!mIsRunning) return;
-
- // Else, BluetoothServerSocket encountered exception.
- Log.e(TAG, "BluetoothServerSocket#accept failed", e);
+ // Note calling BluetoothServerSocket#close() also triggers an IOException
+ // which is indistinguishable from any other exceptional behavior.
+ // postDestroyAndUnregisterReservedOffer() is always safe to call as it
+ // first checks whether the offer still exists; so if the
+ // BluetoothServerSocket was closed (i.e. on tearDown()) this is a noop.
+ Log.w(TAG, "BluetoothServerSocket closed or #accept failed", e);
postDestroyAndUnregisterReservedOffer();
return; // stop running immediately on error
}
@@ -313,7 +314,6 @@
public void tearDown() {
HandlerUtils.ensureRunningOnHandlerThread(mHandler);
- mIsRunning = false;
try {
// BluetoothServerSocket.close() is thread-safe.
mServerSocket.close();
@@ -434,7 +434,6 @@
private class ConnectThread extends Thread {
private final L2capNetworkSpecifier mSpecifier;
private final BluetoothSocket mSocket;
- private volatile boolean mIsAborted = false;
public ConnectThread(L2capNetworkSpecifier specifier, BluetoothSocket socket) {
super("L2capNetworkProvider-ConnectThread");
@@ -451,11 +450,12 @@
if (!success) closeBluetoothSocket(mSocket);
});
} catch (IOException e) {
- Log.e(TAG, "Failed to connect", e);
- if (mIsAborted) return;
-
+ Log.w(TAG, "BluetoothSocket was closed or #connect failed", e);
+ // It is safe to call BluetoothSocket#close() multiple times.
closeBluetoothSocket(mSocket);
mHandler.post(() -> {
+ // Note that if the Socket was closed, this call is a noop as the
+ // ClientNetworkRequest has already been removed.
declareAllNetworkRequestsUnfulfillable(mSpecifier);
});
}
@@ -463,7 +463,6 @@
public void abort() {
HandlerUtils.ensureRunningOnHandlerThread(mHandler);
- mIsAborted = true;
// Closing the BluetoothSocket is the only way to unblock connect() because it calls
// shutdown on the underlying (connected) SOCK_SEQPACKET.
// It is safe to call BluetoothSocket#close() multiple times.
@@ -680,6 +679,11 @@
L2capPacketForwarder.ICallback cb) {
return new L2capPacketForwarder(handler, tunFd, socket, compressHeaders, cb);
}
+
+ /** Create an L2capIpClient */
+ public L2capIpClient createL2capIpClient(String logTag, Context context, String ifname) {
+ return new L2capIpClient(logTag, context, ifname);
+ }
}
public L2capNetworkProvider(Context context) {
diff --git a/service/src/com/android/server/net/L2capNetwork.java b/service/src/com/android/server/net/L2capNetwork.java
index c7417f9..ca155db 100644
--- a/service/src/com/android/server/net/L2capNetwork.java
+++ b/service/src/com/android/server/net/L2capNetwork.java
@@ -52,7 +52,7 @@
*
* Note that the IpClient does not need to be stopped.
*/
- private static class L2capIpClient extends IpClientCallbacks {
+ public static class L2capIpClient extends IpClientCallbacks {
private final String mLogTag;
private final ConditionVariable mOnIpClientCreatedCv = new ConditionVariable(false);
private final ConditionVariable mOnProvisioningSuccessCv = new ConditionVariable(false);
@@ -61,7 +61,7 @@
@Nullable
private volatile LinkProperties mLinkProperties;
- L2capIpClient(String logTag, Context context, String ifname) {
+ public L2capIpClient(String logTag, Context context, String ifname) {
mLogTag = logTag;
IpClientUtil.makeIpClient(context, ifname, this);
}
@@ -157,7 +157,7 @@
// LinkProperties) or fails (and returns null).
// Note that since L2capNetwork is using IPv6 link-local provisioning the most likely
// (only?) failure mode is due to the interface disappearing.
- final LinkProperties lp = new L2capIpClient(logTag, context, ifname).start();
+ final LinkProperties lp = deps.createL2capIpClient(logTag, context, ifname).start();
if (lp == null) return null;
return new L2capNetwork(
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
index 489c3ad..babcba9 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
@@ -17,25 +17,37 @@
package com.android.server
import android.bluetooth.BluetoothAdapter
-import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothServerSocket
import android.bluetooth.BluetoothSocket
+import android.net.INetworkMonitor
+import android.net.INetworkMonitorCallbacks
+import android.net.IpPrefix
import android.net.L2capNetworkSpecifier
import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_NONE
+import android.net.L2capNetworkSpecifier.ROLE_CLIENT
import android.net.L2capNetworkSpecifier.ROLE_SERVER
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.MacAddress
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
import android.net.NetworkRequest
import android.net.NetworkSpecifier
+import android.net.RouteInfo
import android.os.Build
import android.os.HandlerThread
+import android.os.ParcelFileDescriptor
+import com.android.server.net.L2capNetwork.L2capIpClient
+import com.android.server.net.L2capPacketForwarder
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.RecorderCallback.CallbackEntry.Reserved
import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.anyNetwork
import com.android.testutils.waitForIdle
import java.io.IOException
import java.util.Optional
@@ -47,10 +59,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.doThrow
import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
private const val PSM = 0x85
private val REMOTE_MAC = byteArrayOf(1, 2, 3, 4, 5, 6)
@@ -64,10 +79,17 @@
@IgnoreUpTo(Build.VERSION_CODES.R)
@DevSdkIgnoreRunner.MonitorThreadLeak
class CSL2capProviderTest : CSTest() {
+ private val networkMonitor = mock<INetworkMonitor>()
+
private val btAdapter = mock<BluetoothAdapter>()
+ private val btDevice = mock<BluetoothDevice>()
private val btServerSocket = mock<BluetoothServerSocket>()
private val btSocket = mock<BluetoothSocket>()
+ private val tunInterface = mock<ParcelFileDescriptor>()
+ private val l2capIpClient = mock<L2capIpClient>()
+ private val packetForwarder = mock<L2capPacketForwarder>()
private val providerDeps = mock<L2capNetworkProvider.Dependencies>()
+
// BlockingQueue does not support put(null) operations, as null is used as an internal sentinel
// value. Therefore, use Optional<BluetoothSocket> where an empty optional signals the
// BluetoothServerSocket#close() operation.
@@ -84,6 +106,8 @@
doReturn(btAdapter).`when`(bluetoothManager).getAdapter()
doReturn(btServerSocket).`when`(btAdapter).listenUsingInsecureL2capChannel()
doReturn(PSM).`when`(btServerSocket).getPsm()
+ doReturn(btDevice).`when`(btAdapter).getRemoteDevice(eq(REMOTE_MAC))
+ doReturn(btSocket).`when`(btDevice).createInsecureL2capChannel(eq(PSM))
doAnswer {
val sock = acceptQueue.take()
@@ -96,6 +120,30 @@
}.`when`(btServerSocket).close()
doReturn(handlerThread).`when`(providerDeps).getHandlerThread()
+ doReturn(tunInterface).`when`(providerDeps).createTunInterface(any())
+ doReturn(packetForwarder).`when`(providerDeps)
+ .createL2capPacketForwarder(any(), any(), any(), any(), any())
+ doReturn(l2capIpClient).`when`(providerDeps).createL2capIpClient(any(), any(), any())
+
+ val lp = LinkProperties()
+ val ifname = "l2cap-tun0"
+ lp.setInterfaceName(ifname)
+ lp.addLinkAddress(LinkAddress("fe80::1/64"))
+ lp.addRoute(RouteInfo(IpPrefix("fe80::/64"), null /* nextHop */, ifname))
+ doReturn(lp).`when`(l2capIpClient).start()
+
+ // Note: In order to properly register a NetworkAgent, a NetworkMonitor must be created for
+ // the agent. CSAgentWrapper already does some of this, but requires adding additional
+ // Dependencies to the production code. Create a mocked NM inside this test instead.
+ doAnswer { i ->
+ val cb = i.arguments[2] as INetworkMonitorCallbacks
+ cb.onNetworkMonitorCreated(networkMonitor)
+ }.`when`(networkStack).makeNetworkMonitor(
+ any() /* network */,
+ isNull() /* name */,
+ any() /* callbacks */
+ )
+
provider = L2capNetworkProvider(providerDeps, context)
provider.start()
}
@@ -241,4 +289,109 @@
cb2.expect<Reserved>()
cb2.assertNoCallback()
}
+
+ @Test
+ fun testServerNetwork() {
+ val specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .build()
+ val nr = REQUEST.copyWithSpecifier(specifier)
+ val cb = reserveNetwork(nr)
+ cb.expect<Reserved>()
+
+ // Unblock BluetoothServerSocket#accept()
+ doReturn(true).`when`(btSocket).isConnected()
+ acceptQueue.put(Optional.of(btSocket))
+
+ cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+ cb.assertNoCallback()
+ // Verify that packet forwarding was started.
+ // TODO: stop mocking L2capPacketForwarder.
+ verify(providerDeps).createL2capPacketForwarder(any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testBluetoothException_createInsecureL2capChannelThrows() {
+ doThrow(IOException()).`when`(btDevice).createInsecureL2capChannel(any())
+
+ val specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+ .setPsm(PSM)
+ .build()
+ val nr = REQUEST.copyWithSpecifier(specifier)
+ val cb = requestNetwork(nr)
+
+ cb.expect<Unavailable>()
+ }
+
+ @Test
+ fun testBluetoothException_bluetoothSocketConnectThrows() {
+ doThrow(IOException()).`when`(btSocket).connect()
+
+ val specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+ .setPsm(PSM)
+ .build()
+ val nr = REQUEST.copyWithSpecifier(specifier)
+ val cb = requestNetwork(nr)
+
+ cb.expect<Unavailable>()
+ }
+
+ @Test
+ fun testClientNetwork() {
+ val specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+ .setPsm(PSM)
+ .build()
+ val nr = REQUEST.copyWithSpecifier(specifier)
+ val cb = requestNetwork(nr)
+ cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+ }
+
+ @Test
+ fun testClientNetwork_headerCompressionMismatch() {
+ var specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+ .setPsm(PSM)
+ .build()
+ var nr = REQUEST.copyWithSpecifier(specifier)
+ val cb = requestNetwork(nr)
+ cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+ .setPsm(PSM)
+ .build()
+ nr = REQUEST.copyWithSpecifier(specifier)
+ val cb2 = requestNetwork(nr)
+ cb2.expect<Unavailable>()
+ }
+
+ @Test
+ fun testClientNetwork_multipleRequests() {
+ val specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+ .setPsm(PSM)
+ .build()
+ val nr = REQUEST.copyWithSpecifier(specifier)
+ val cb = requestNetwork(nr)
+ cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+
+ val cb2 = requestNetwork(nr)
+ cb2.expectAvailableCallbacks(anyNetwork(), validated = false)
+ }
}