Set up forwarding rules for local network agents

Test: CSLocalAgentTests, new tests for this
Change-Id: I8994af350a1799ab5f6ebb2872f2abfaf174bd61
This commit is contained in:
Chalard Jean
2023-10-06 18:45:53 +09:00
parent 95cf7f9550
commit 1e4c218c28
5 changed files with 440 additions and 13 deletions

View File

@@ -21,13 +21,19 @@ import android.net.LinkAddress
import android.net.LinkProperties
import android.net.LocalNetworkConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_DUN
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import android.net.NetworkScore
import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
import android.net.NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK
import android.net.RouteInfo
import android.os.Build
import com.android.testutils.DevSdkIgnoreRule
@@ -40,8 +46,17 @@ import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
import kotlin.test.assertFailsWith
private const val TIMEOUT_MS = 200L
private const val MEDIUM_TIMEOUT_MS = 1_000L
private const val LONG_TIMEOUT_MS = 5_000
private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
addTransportType(transport)
caps.forEach {
@@ -60,6 +75,12 @@ private fun lp(iface: String) = LinkProperties().apply {
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
}
// This allows keeping all the networks connected without having to file individual requests
// for them.
private fun keepScore() = FromS(
NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build()
)
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
class CSLocalAgentTests : CSTest() {
@@ -125,7 +146,8 @@ class CSLocalAgentTests : CSTest() {
cb)
// Set up a local agent that should forward its traffic to the best DUN upstream.
val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
val localAgent = Agent(
nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
lnc = LocalNetworkConfig.Builder().build(),
)
@@ -145,4 +167,242 @@ class CSLocalAgentTests : CSTest() {
localAgent.disconnect()
}
@Test
fun testUnregisterUpstreamAfterReplacement_SameIfaceName() {
doTestUnregisterUpstreamAfterReplacement(true)
}
@Test
fun testUnregisterUpstreamAfterReplacement_DifferentIfaceName() {
doTestUnregisterUpstreamAfterReplacement(false)
}
fun doTestUnregisterUpstreamAfterReplacement(sameIfaceName: Boolean) {
deps.setBuildSdk(VERSION_V)
val cb = TestableNetworkCallback()
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
// Set up a local agent that should forward its traffic to the best wifi upstream.
val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
lnc = LocalNetworkConfig.Builder()
.setUpstreamSelector(NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.build())
.build(),
score = FromS(NetworkScore.Builder()
.setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
.build())
)
localAgent.connect()
cb.expectAvailableCallbacks(localAgent.network, validated = false)
val wifiAgent = Agent(lp = lp("wifi0"),
nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
wifiAgent.connect()
cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
clearInvocations(netd)
val inOrder = inOrder(netd)
wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
waitForIdle()
inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi0")
inOrder.verify(netd).networkDestroy(wifiAgent.network.netId)
val wifiIface2 = if (sameIfaceName) "wifi0" else "wifi1"
val wifiAgent2 = Agent(lp = lp(wifiIface2),
nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
wifiAgent2.connect()
cb.expectAvailableCallbacks(wifiAgent2.network, validated = false)
cb.expect<Lost> { it.network == wifiAgent.network }
inOrder.verify(netd).ipfwdAddInterfaceForward("local0", wifiIface2)
if (sameIfaceName) {
inOrder.verify(netd, never()).ipfwdRemoveInterfaceForward(any(), any())
}
}
@Test
fun testUnregisterUpstreamAfterReplacement_neverReplaced() {
deps.setBuildSdk(VERSION_V)
val cb = TestableNetworkCallback()
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
// Set up a local agent that should forward its traffic to the best wifi upstream.
val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
lnc = LocalNetworkConfig.Builder()
.setUpstreamSelector(NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.build())
.build(),
score = FromS(NetworkScore.Builder()
.setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
.build())
)
localAgent.connect()
cb.expectAvailableCallbacks(localAgent.network, validated = false)
val wifiAgent = Agent(lp = lp("wifi0"),
nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
wifiAgent.connect()
cb.expectAvailableCallbacksUnvalidated(wifiAgent)
clearInvocations(netd)
wifiAgent.unregisterAfterReplacement(TIMEOUT_MS.toInt())
waitForIdle()
verify(netd).networkDestroy(wifiAgent.network.netId)
verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi0")
cb.expect<Lost> { it.network == wifiAgent.network }
}
@Test
fun testUnregisterLocalAgentAfterReplacement() {
deps.setBuildSdk(VERSION_V)
val localCb = TestableNetworkCallback()
cm.requestNetwork(NetworkRequest.Builder().clearCapabilities()
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
.build(),
localCb)
val cb = TestableNetworkCallback()
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
val localNc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK)
val lnc = LocalNetworkConfig.Builder()
.setUpstreamSelector(NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.build())
.build()
val localScore = FromS(NetworkScore.Builder().build())
// Set up a local agent that should forward its traffic to the best wifi upstream.
val localAgent = Agent(nc = localNc, lp = lp("local0"), lnc = lnc, score = localScore)
localAgent.connect()
localCb.expectAvailableCallbacks(localAgent.network, validated = false)
cb.expectAvailableCallbacks(localAgent.network, validated = false)
val wifiAgent = Agent(lp = lp("wifi0"), nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
wifiAgent.connect()
cb.expectAvailableCallbacksUnvalidated(wifiAgent)
verify(netd).ipfwdAddInterfaceForward("local0", "wifi0")
localAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
val localAgent2 = Agent(nc = localNc, lp = lp("local0"), lnc = lnc, score = localScore)
localAgent2.connect()
localCb.expectAvailableCallbacks(localAgent2.network, validated = false)
cb.expectAvailableCallbacks(localAgent2.network, validated = false)
cb.expect<Lost> { it.network == localAgent.network }
}
@Test
fun testDestroyedNetworkAsSelectedUpstream() {
deps.setBuildSdk(VERSION_V)
val cb = TestableNetworkCallback()
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
val wifiAgent = Agent(lp = lp("wifi0"), nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
wifiAgent.connect()
cb.expectAvailableCallbacksUnvalidated(wifiAgent)
// Set up a local agent that should forward its traffic to the best wifi upstream.
val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
lnc = LocalNetworkConfig.Builder()
.setUpstreamSelector(NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.build())
.build(),
score = FromS(NetworkScore.Builder()
.setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
.build())
)
// ...but destroy the wifi agent before connecting it
wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
localAgent.connect()
cb.expectAvailableCallbacks(localAgent.network, validated = false)
verify(netd).ipfwdAddInterfaceForward("local0", "wifi0")
verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi0")
}
@Test
fun testForwardingRules() {
deps.setBuildSdk(VERSION_V)
// Set up a local agent that should forward its traffic to the best DUN upstream.
val lnc = LocalNetworkConfig.Builder()
.setUpstreamSelector(NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_DUN)
.build())
.build()
val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
lnc = lnc,
score = FromS(NetworkScore.Builder()
.setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
.build())
)
localAgent.connect()
val wifiAgent = Agent(score = keepScore(), lp = lp("wifi0"),
nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
val cellAgentDun = Agent(score = keepScore(), lp = lp("cell0"),
nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
val wifiAgentDun = Agent(score = keepScore(), lp = lp("wifi1"),
nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
val inOrder = inOrder(netd)
inOrder.verify(netd, never()).ipfwdAddInterfaceForward(any(), any())
wifiAgent.connect()
inOrder.verify(netd, never()).ipfwdAddInterfaceForward(any(), any())
cellAgentDun.connect()
inOrder.verify(netd).ipfwdEnableForwarding(any())
inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "cell0")
wifiAgentDun.connect()
inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "cell0")
inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi1")
// Make sure sending the same config again doesn't do anything
repeat(5) {
localAgent.sendLocalNetworkConfig(lnc)
}
inOrder.verifyNoMoreInteractions()
wifiAgentDun.disconnect()
inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi1")
// This can take a little bit of time because it needs to wait for the rematch
inOrder.verify(netd, timeout(MEDIUM_TIMEOUT_MS)).ipfwdAddInterfaceForward("local0", "cell0")
cellAgentDun.disconnect()
inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "cell0")
inOrder.verify(netd).ipfwdDisableForwarding(any())
val wifiAgentDun2 = Agent(score = keepScore(), lp = lp("wifi2"),
nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
wifiAgentDun2.connect()
inOrder.verify(netd).ipfwdEnableForwarding(any())
inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi2")
localAgent.disconnect()
inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi2")
inOrder.verify(netd).ipfwdDisableForwarding(any())
}
}

View File

@@ -168,6 +168,7 @@ class CSAgentWrapper(
}
fun unregisterAfterReplacement(timeoutMs: Int) = agent.unregisterAfterReplacement(timeoutMs)
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc)
}