summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorNiklas Berglund <niklas.berglund@gmail.com>2024-11-20 16:31:37 +0100
committerNiklas Berglund <niklas.berglund@gmail.com>2024-12-03 15:15:37 +0100
commita83948cbb4be5c02c90ebddf8068a06e0e892fb8 (patch)
treea5ade65e3c9e2999dd3e9bfc04deda07e65ab8ff /android
parent7b0f970d286089d6d00ede630e275624a8b84022 (diff)
downloadmullvadvpn-a83948cbb4be5c02c90ebddf8068a06e0e892fb8.tar.xz
mullvadvpn-a83948cbb4be5c02c90ebddf8068a06e0e892fb8.zip
Implement UDP-over-TCP e2e test and firewall API client
Diffstat (limited to 'android')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt32
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt4
-rw-r--r--android/gradle.properties1
-rwxr-xr-xandroid/scripts/run-instrumented-tests.sh4
-rw-r--r--android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt4
-rw-r--r--android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt3
-rw-r--r--android/test/e2e/build.gradle.kts6
-rw-r--r--android/test/e2e/e2e.properties2
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt129
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt4
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/HasDependencyOnLocalAPI.kt3
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/ClearFirewallRules.kt24
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/LeakCheck.kt2
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/NetworkingProtocol.kt11
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/DropRule.kt26
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/FirewallClient.kt73
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/SessionIdentifier.kt28
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Host.kt (renamed from android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Host.kt)2
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Packet.kt (renamed from android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Packet.kt)2
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/PacketCapture.kt (renamed from android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt)6
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Stream.kt (renamed from android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Stream.kt)12
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketCaptureSessionSerializer.kt2
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketSerializer.kt6
26 files changed, 352 insertions, 48 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt
index 45edfcd204..19802571f6 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt
@@ -16,7 +16,7 @@ import net.mullvad.mullvadvpn.compose.state.VpnSettingsUiState
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_LAST_ITEM_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG
-import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_VPN_SETTINGS_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_OBFUSCATION_TITLE_TEST_TAG
@@ -47,7 +47,7 @@ class VpnSettingsScreenTest {
// Arrange
setContentWithTheme { VpnSettingsScreen(state = VpnSettingsUiState.createDefault()) }
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG))
// Assert
@@ -70,7 +70,7 @@ class VpnSettingsScreenTest {
)
}
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG))
// Assert
@@ -117,7 +117,7 @@ class VpnSettingsScreenTest {
)
)
}
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG))
// Assert
onNodeWithText(DUMMY_DNS_ADDRESS).assertDoesNotExist()
@@ -213,7 +213,7 @@ class VpnSettingsScreenTest {
)
)
}
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG))
// Assert
@@ -236,7 +236,7 @@ class VpnSettingsScreenTest {
onSelectQuantumResistanceSetting = mockSelectQuantumResistantSettingListener,
)
}
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG))
// Assert
@@ -261,7 +261,7 @@ class VpnSettingsScreenTest {
}
// Act
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(
hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53))
)
@@ -291,7 +291,7 @@ class VpnSettingsScreenTest {
}
// Act
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(
hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53))
)
@@ -318,7 +318,7 @@ class VpnSettingsScreenTest {
}
// Act
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG))
// Assert
@@ -342,7 +342,7 @@ class VpnSettingsScreenTest {
}
// Act
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG))
onNodeWithTag(testTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG).performClick()
@@ -364,7 +364,7 @@ class VpnSettingsScreenTest {
)
}
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG))
// Act
@@ -407,7 +407,7 @@ class VpnSettingsScreenTest {
}
// Act
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_OBFUSCATION_TITLE_TEST_TAG))
onNodeWithText("WireGuard obfuscation").performClick()
@@ -430,7 +430,7 @@ class VpnSettingsScreenTest {
// Act
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG))
onNodeWithText("Quantum-resistant tunnel").performClick()
@@ -469,7 +469,7 @@ class VpnSettingsScreenTest {
)
}
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG))
onNodeWithText("Custom").performClick()
@@ -490,7 +490,7 @@ class VpnSettingsScreenTest {
}
// Act
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG))
onNodeWithTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG).performClick()
@@ -514,7 +514,7 @@ class VpnSettingsScreenTest {
}
// Act
- onNodeWithTag(LAZY_LIST_TEST_TAG)
+ onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG))
onNodeWithTag(testTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG).performClick()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt
index 6688f5d0ab..5191eba331 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt
@@ -19,6 +19,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -57,12 +58,14 @@ fun ObfuscationModeCell(
isSelected: Boolean,
onSelected: (ObfuscationMode) -> Unit,
onNavigate: () -> Unit = {},
+ testTag: String? = null,
) {
Row(
modifier =
Modifier.height(IntrinsicSize.Min)
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceContainerLow)
+ .let { if (testTag != null) it.testTag(testTag) else it }
) {
TwoRowCell(
modifier = Modifier.weight(1f),
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt
index 3ec353bc96..7d4e2a5838 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt
@@ -16,6 +16,7 @@ import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.screen.ConnectionDetails
+import net.mullvad.mullvadvpn.compose.test.LOCATION_INFO_CONNECTION_IN_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LOCATION_INFO_CONNECTION_OUT_TEST_TAG
import net.mullvad.mullvadvpn.constant.SPACE_CHAR
import net.mullvad.mullvadvpn.lib.model.TransportProtocol
@@ -88,7 +89,7 @@ fun ConnectionDetails(
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier =
- Modifier.constrainAs(inAddr) {
+ Modifier.testTag(LOCATION_INFO_CONNECTION_IN_TEST_TAG).constrainAs(inAddr) {
start.linkTo(headerBarrier)
end.linkTo(parent.end)
top.linkTo(parent.top)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
index f763272438..6489b53e79 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
@@ -82,11 +82,13 @@ import net.mullvad.mullvadvpn.compose.state.VpnSettingsUiState
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_LAST_ITEM_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG
-import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_VPN_SETTINGS_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_OBFUSCATION_TITLE_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.WIREGUARD_OBFUSCATION_OFF_CELL
+import net.mullvad.mullvadvpn.compose.test.WIREGUARD_OBFUSCATION_UDP_OVER_TCP_CELL
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
import net.mullvad.mullvadvpn.compose.util.OnNavResultValue
@@ -297,7 +299,7 @@ fun VpnSettingsScreen(
snackbarHostState = snackbarHostState,
) { modifier, lazyListState ->
LazyColumn(
- modifier = modifier.testTag(LAZY_LIST_TEST_TAG).animateContentSize(),
+ modifier = modifier.testTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG).animateContentSize(),
state = lazyListState,
) {
if (state.systemVpnSettingsAvailable) {
@@ -568,6 +570,7 @@ fun VpnSettingsScreen(
port = state.selectedUdp2TcpObfuscationPort,
onSelected = onSelectObfuscationMode,
onNavigate = navigateToUdp2TcpSettings,
+ testTag = WIREGUARD_OBFUSCATION_UDP_OVER_TCP_CELL,
)
}
itemWithDivider {
@@ -575,6 +578,7 @@ fun VpnSettingsScreen(
title = stringResource(id = R.string.off),
isSelected = state.obfuscationMode == ObfuscationMode.Off,
onCellClicked = { onSelectObfuscationMode(ObfuscationMode.Off) },
+ testTag = WIREGUARD_OBFUSCATION_OFF_CELL,
)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
index d90f14a763..b124ffcc61 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
@@ -5,6 +5,7 @@ const val TOP_BAR_ACCOUNT_BUTTON = "top_bar_account_button"
const val TOP_BAR_SETTINGS_BUTTON = "top_bar_settings_button"
// VpnSettingsScreen
+const val LAZY_LIST_VPN_SETTINGS_TEST_TAG = "lazy_list_vpn_settings_test_tag"
const val LAZY_LIST_TEST_TAG = "lazy_list_test_tag"
const val LAZY_LIST_LAST_ITEM_TEST_TAG = "lazy_list_last_item_test_tag"
const val LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG = "lazy_list_quantum_item_off_test_tag"
@@ -18,6 +19,9 @@ const val CUSTOM_PORT_DIALOG_INPUT_TEST_TAG = "custom_port_dialog_input_test_tag
const val LAZY_LIST_WIREGUARD_OBFUSCATION_TITLE_TEST_TAG =
"lazy_list_wireguard_obfuscation_title_test_tag"
const val SWITCH_TEST_TAG = "switch_test_tag"
+const val WIREGUARD_OBFUSCATION_OFF_CELL = "wireguard_obfuscation_off_cell_test_tag"
+const val WIREGUARD_OBFUSCATION_UDP_OVER_TCP_CELL =
+ "wireguard_obfuscation_udp_over_tcp_cell_test_tag"
// SelectLocationScreen, ConnectScreen, CustomListLocationsScreen
const val CIRCULAR_PROGRESS_INDICATOR = "circular_progress_indicator"
diff --git a/android/gradle.properties b/android/gradle.properties
index 74c277e3a1..b8a39d5d02 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -3,3 +3,4 @@ android.nonTransitiveRClass=false
android.useAndroidX=true
kotlin.code.style=official
org.gradle.jvmargs=-Xmx8192M -Dkotlin.daemon.jvm.options\="-Xmx8192M"
+test.e2e.enableAccessToLocalApiTests=false
diff --git a/android/scripts/run-instrumented-tests.sh b/android/scripts/run-instrumented-tests.sh
index 055112fb43..8d835ddb5a 100755
--- a/android/scripts/run-instrumented-tests.sh
+++ b/android/scripts/run-instrumented-tests.sh
@@ -150,6 +150,8 @@ if [[ -z $REPORT_DIR || ! -d $REPORT_DIR ]]; then
exit 1
fi
+GRADLE_ENVIRONMENT_VARIABLES="TEST_E2E_ENABLEACCESSTOLOCALAPITESTS=$ENABLE_ACCESS_TO_LOCAL_API_TESTS"
+
INSTRUMENTATION_LOG_FILE_PATH="$REPORT_DIR/instrumentation-log.txt"
LOGCAT_FILE_PATH="$REPORT_DIR/logcat.txt"
LOCAL_SCREENSHOT_PATH="$REPORT_DIR/screenshots"
@@ -222,7 +224,7 @@ else
-e runnerBuilder de.mannodermaus.junit5.AndroidJUnit5Builder \
$TEST_PACKAGE_NAME/androidx.test.runner.AndroidJUnitRunner"
fi
-adb shell "$INSTRUMENTATION_COMMAND" | tee "$INSTRUMENTATION_LOG_FILE_PATH"
+adb shell "$GRADLE_ENVIRONMENT_VARIABLES $INSTRUMENTATION_COMMAND" | tee "$INSTRUMENTATION_LOG_FILE_PATH"
echo ""
echo "### Ensure that packages are uninstalled ###"
diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt
index 34690022c9..ea9b761ea1 100644
--- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt
+++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt
@@ -96,7 +96,7 @@ class AppInteractor(
}
fun extractInIpv4Address(): String {
- device.findObjectWithTimeout(By.res("location_info_test_tag")).click()
+ device.findObjectWithTimeout(By.res("connect_card_header_test_tag")).click()
val inString =
device
.findObjectWithTimeout(
@@ -105,7 +105,7 @@ class AppInteractor(
)
.text
- val extractedIpAddress = inString.split(" ")[1].split(":")[0]
+ val extractedIpAddress = inString.split(" ")[0].split(":")[0]
return extractedIpAddress
}
diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt
index c96718da61..2a4a4dfeb1 100644
--- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt
+++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt
@@ -45,6 +45,8 @@ class ForgetAllVpnAppsInSettingsTestRule : BeforeTestExecutionCallback {
device
.findObjectWithTimeout(By.text(DELETE_VPN_CONFIRM_BUTTON_TEXT_REGEXP))
.click()
+ } else if (device.hasObjectWithTimeout(By.text(FORGET_VPN_BUTTON_TEXT))) {
+ device.findObjectWithTimeout(By.text(FORGET_VPN_BUTTON_TEXT)).click()
} else {
fail("Unable to find forget or delete button")
}
@@ -60,6 +62,7 @@ class ForgetAllVpnAppsInSettingsTestRule : BeforeTestExecutionCallback {
private val HARDCODED_VPN_PROFILE_NAMES = listOf("VPN by Google")
private const val FORGET_VPN_VPN_BUTTON_TEXT = "Forget VPN"
+ private const val FORGET_VPN_BUTTON_TEXT = "Forget" // Legacy VPN
private const val DELETE_VPN_PROFILE_TEXT = "Delete VPN profile"
private const val FORGET_VPN_VPN_CONFIRM_BUTTON_TEXT = "Forget"
// Samsung S22 shows "Delete"
diff --git a/android/test/e2e/build.gradle.kts b/android/test/e2e/build.gradle.kts
index 4b1b2b9d60..500a1e27cd 100644
--- a/android/test/e2e/build.gradle.kts
+++ b/android/test/e2e/build.gradle.kts
@@ -36,7 +36,7 @@ android {
load(project.file("e2e.properties").inputStream())
addRequiredPropertyAsBuildConfigField("API_VERSION")
addRequiredPropertyAsBuildConfigField("TRAFFIC_GENERATION_IP_ADDRESS")
- addRequiredPropertyAsBuildConfigField("PACKET_CAPTURE_API_HOST")
+ addRequiredPropertyAsBuildConfigField("TEST_ROUTER_API_HOST")
}
fun MutableMap<String, String>.addOptionalPropertyAsArgument(name: String) {
@@ -52,10 +52,12 @@ android {
testInstrumentationRunnerArguments +=
mutableMapOf<String, String>().apply {
put("clearPackageData", "true")
- addOptionalPropertyAsArgument("enable_access_to_local_api_tests")
addOptionalPropertyAsArgument("enable_highly_rate_limited_tests")
addOptionalPropertyAsArgument("valid_test_account_number")
addOptionalPropertyAsArgument("invalid_test_account_number")
+ project.findProperty("test.e2e.enableAccessToLocalApiTests")?.let {
+ put("enable_access_to_local_api_tests", it.toString())
+ }
}
}
diff --git a/android/test/e2e/e2e.properties b/android/test/e2e/e2e.properties
index db9bdd29a5..1cfa0f6bd1 100644
--- a/android/test/e2e/e2e.properties
+++ b/android/test/e2e/e2e.properties
@@ -1,3 +1,3 @@
API_VERSION=v1
TRAFFIC_GENERATION_IP_ADDRESS=45.83.223.209
-PACKET_CAPTURE_API_HOST=192.168.105.1
+TEST_ROUTER_API_HOST=192.168.105.1
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt
index 7b98c2f8f3..81a9eabfe6 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt
@@ -1,13 +1,27 @@
package net.mullvad.mullvadvpn.test.e2e
import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Direction
+import androidx.test.uiautomator.Until
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
import net.mullvad.mullvadvpn.BuildConfig
+import net.mullvad.mullvadvpn.compose.test.EXPAND_BUTTON_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_BUTTON_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.SWITCH_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.TOP_BAR_SETTINGS_BUTTON
+import net.mullvad.mullvadvpn.test.common.constant.EXTREMELY_LONG_TIMEOUT
import net.mullvad.mullvadvpn.test.common.constant.VERY_LONG_TIMEOUT
import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout
import net.mullvad.mullvadvpn.test.common.rule.ForgetAllVpnAppsInSettingsTestRule
+import net.mullvad.mullvadvpn.test.e2e.annotations.HasDependencyOnLocalAPI
import net.mullvad.mullvadvpn.test.e2e.misc.AccountTestRule
+import net.mullvad.mullvadvpn.test.e2e.misc.ClearFirewallRules
import net.mullvad.mullvadvpn.test.e2e.misc.ConnCheckState
import net.mullvad.mullvadvpn.test.e2e.misc.SimpleMullvadHttpClient
+import net.mullvad.mullvadvpn.test.e2e.router.firewall.DropRule
+import net.mullvad.mullvadvpn.test.e2e.router.firewall.FirewallClient
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
@@ -20,6 +34,8 @@ class ConnectionTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) {
@JvmField
val forgetAllVpnAppsInSettingsTestRule = ForgetAllVpnAppsInSettingsTestRule()
+ val firewallClient = FirewallClient()
+
@Test
fun testConnect() {
// Given
@@ -48,4 +64,117 @@ class ConnectionTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) {
val result = SimpleMullvadHttpClient(targetContext).runConnectionCheck()
assertEquals(expected, result)
}
+
+ @Test
+ @HasDependencyOnLocalAPI
+ @ClearFirewallRules
+ fun testWireGuardObfuscationOff() = runBlocking {
+ app.launchAndEnsureLoggedIn(accountTestRule.validAccountNumber)
+
+ enableLocalNetworkSharing()
+
+ device.findObjectWithTimeout(By.res(SELECT_LOCATION_BUTTON_TEST_TAG)).click()
+ clickLocationExpandButton(DEFAULT_COUNTRY)
+ clickLocationExpandButton(DEFAULT_CITY)
+ device.findObjectWithTimeout(By.text(DEFAULT_RELAY)).click()
+ device.findObjectWithTimeout(By.text("OK")).click()
+ device.findObjectWithTimeout(By.text("CONNECTED"), VERY_LONG_TIMEOUT)
+ val relayIpAddress = app.extractInIpv4Address()
+ device.findObjectWithTimeout(By.text("Disconnect")).click()
+
+ // Disable obfuscation
+ device.findObjectWithTimeout(By.res(TOP_BAR_SETTINGS_BUTTON)).click()
+ device.findObjectWithTimeout(By.text("VPN settings")).click()
+ val scrollView = device.findObjectWithTimeout(By.res(SETTINGS_SCROLL_VIEW_TEST_TAG))
+ scrollView.scrollUntil(
+ Direction.DOWN,
+ Until.hasObject(By.res(WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG)),
+ )
+ device.findObjectWithTimeout(By.res(WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG)).click()
+ device.pressBack()
+ device.pressBack()
+
+ // Block UDP traffic to the relay
+ val firewallRule = DropRule.blockUDPTrafficRule(relayIpAddress)
+ firewallClient.createRule(firewallRule)
+
+ // Ensure it is not possible to connect to relay
+ device.findObjectWithTimeout(By.text("Connect")).click()
+ // Give it some time and then verify still unable to connect. This duration must be long
+ // enough to ensure all retry attempts have been made.
+ delay(UNSUCCESSFUL_CONNECTION_TIMEOUT.milliseconds)
+ device.findObjectWithTimeout(By.text(("CONNECTING...")))
+ device.findObjectWithTimeout(By.text("Cancel")).click()
+ }
+
+ @Test
+ @HasDependencyOnLocalAPI
+ @ClearFirewallRules
+ fun testUDPOverTCP() =
+ runBlocking<Unit> {
+ app.launchAndEnsureLoggedIn(accountTestRule.validAccountNumber)
+
+ enableLocalNetworkSharing()
+
+ device.findObjectWithTimeout(By.res(SELECT_LOCATION_BUTTON_TEST_TAG)).click()
+ clickLocationExpandButton(DEFAULT_COUNTRY)
+ clickLocationExpandButton(DEFAULT_CITY)
+ device.findObjectWithTimeout(By.text(DEFAULT_RELAY)).click()
+ device.findObjectWithTimeout(By.text("OK")).click()
+ device.findObjectWithTimeout(By.text("CONNECTED"), VERY_LONG_TIMEOUT)
+ val relayIpAddress = app.extractInIpv4Address()
+ device.findObjectWithTimeout(By.text("Disconnect")).click()
+
+ // Block UDP traffic to the relay
+ val firewallRule = DropRule.blockUDPTrafficRule(relayIpAddress)
+ firewallClient.createRule(firewallRule)
+
+ // Enable UDP-over-TCP
+ device.findObjectWithTimeout(By.res(TOP_BAR_SETTINGS_BUTTON)).click()
+ device.findObjectWithTimeout(By.text("VPN settings")).click()
+ val scrollView2 = device.findObjectWithTimeout(By.res(SETTINGS_SCROLL_VIEW_TEST_TAG))
+ scrollView2.scrollUntil(
+ Direction.DOWN,
+ Until.hasObject(By.res(WIREGUARD_OBFUSCATION_UDP_OVER_TCP_CELL_TEST_TAG)),
+ )
+ device
+ .findObjectWithTimeout(By.res(WIREGUARD_OBFUSCATION_UDP_OVER_TCP_CELL_TEST_TAG))
+ .click()
+ device.pressBack()
+ device.pressBack()
+
+ // Ensure it is possible to connect by using UDP-over-TCP
+ device.findObjectWithTimeout(By.text("Connect")).click()
+ device.findObjectWithTimeout(By.text("CONNECTED"), EXTREMELY_LONG_TIMEOUT)
+ device.findObjectWithTimeout(By.text("Disconnect")).click()
+ }
+
+ private fun enableLocalNetworkSharing() {
+ device.findObjectWithTimeout(By.res(TOP_BAR_SETTINGS_BUTTON)).click()
+ device.findObjectWithTimeout(By.text("VPN settings")).click()
+
+ val localNetworkSharingCell =
+ device.findObjectWithTimeout(By.text("Local network sharing")).parent
+ val localNetworkSharingSwitch =
+ localNetworkSharingCell.findObjectWithTimeout(By.res(SWITCH_TEST_TAG))
+
+ localNetworkSharingSwitch.click()
+ device.pressBack()
+ device.pressBack()
+ }
+
+ private fun clickLocationExpandButton(locationName: String) {
+ val locationCell = device.findObjectWithTimeout(By.text(locationName)).parent.parent
+ val expandButton = locationCell.findObjectWithTimeout(By.res(EXPAND_BUTTON_TEST_TAG))
+ expandButton.click()
+ }
+
+ companion object {
+ const val SETTINGS_SCROLL_VIEW_TEST_TAG = "lazy_list_vpn_settings_test_tag"
+ const val WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG =
+ "wireguard_obfuscation_off_cell_test_tag"
+ const val WIREGUARD_OBFUSCATION_UDP_OVER_TCP_CELL_TEST_TAG =
+ "wireguard_obfuscation_udp_over_tcp_cell_test_tag"
+ const val UNSUCCESSFUL_CONNECTION_TIMEOUT = 60000L
+ }
}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt
index 406671d346..47f2c72068 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt
@@ -16,9 +16,9 @@ import net.mullvad.mullvadvpn.test.e2e.annotations.HasDependencyOnLocalAPI
import net.mullvad.mullvadvpn.test.e2e.misc.AccountTestRule
import net.mullvad.mullvadvpn.test.e2e.misc.LeakCheck
import net.mullvad.mullvadvpn.test.e2e.misc.NoTrafficToHostRule
-import net.mullvad.mullvadvpn.test.e2e.misc.PacketCapture
-import net.mullvad.mullvadvpn.test.e2e.misc.PacketCaptureResult
import net.mullvad.mullvadvpn.test.e2e.misc.TrafficGenerator
+import net.mullvad.mullvadvpn.test.e2e.router.packetCapture.PacketCapture
+import net.mullvad.mullvadvpn.test.e2e.router.packetCapture.PacketCaptureResult
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/HasDependencyOnLocalAPI.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/HasDependencyOnLocalAPI.kt
index c6c9a70ee9..12987df9e0 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/HasDependencyOnLocalAPI.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/HasDependencyOnLocalAPI.kt
@@ -1,7 +1,6 @@
package net.mullvad.mullvadvpn.test.e2e.annotations
import androidx.test.platform.app.InstrumentationRegistry
-import net.mullvad.mullvadvpn.test.e2e.constant.ENABLE_ACCESS_TO_LOCAL_API_TESTS
import net.mullvad.mullvadvpn.test.e2e.extension.getRequiredArgument
import org.junit.jupiter.api.extension.ConditionEvaluationResult
import org.junit.jupiter.api.extension.ExecutionCondition
@@ -22,7 +21,7 @@ annotation class HasDependencyOnLocalAPI {
val enable =
InstrumentationRegistry.getArguments()
- .getRequiredArgument(ENABLE_ACCESS_TO_LOCAL_API_TESTS)
+ .getRequiredArgument("enable_access_to_local_api_tests")
.toBoolean()
return if (enable) {
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/ClearFirewallRules.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/ClearFirewallRules.kt
new file mode 100644
index 0000000000..5fe73e04c7
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/ClearFirewallRules.kt
@@ -0,0 +1,24 @@
+package net.mullvad.mullvadvpn.test.e2e.misc
+
+import kotlinx.coroutines.runBlocking
+import net.mullvad.mullvadvpn.test.e2e.router.firewall.FirewallClient
+import org.junit.jupiter.api.extension.AfterTestExecutionCallback
+import org.junit.jupiter.api.extension.BeforeTestExecutionCallback
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.api.extension.ExtensionContext
+
+@Retention(AnnotationRetention.RUNTIME)
+@ExtendWith(ClearFirewallRules.ClearFirewallRulesAfterTest::class)
+annotation class ClearFirewallRules {
+ class ClearFirewallRulesAfterTest : BeforeTestExecutionCallback, AfterTestExecutionCallback {
+ val firewallClient = FirewallClient()
+
+ override fun beforeTestExecution(context: ExtensionContext?) {
+ runBlocking { firewallClient.removeAllRules() }
+ }
+
+ override fun afterTestExecution(context: ExtensionContext?) {
+ runBlocking { firewallClient.removeAllRules() }
+ }
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/LeakCheck.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/LeakCheck.kt
index cab83f243c..6770551f65 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/LeakCheck.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/LeakCheck.kt
@@ -1,6 +1,6 @@
package net.mullvad.mullvadvpn.test.e2e.misc
-import net.mullvad.mullvadvpn.test.e2e.model.Stream
+import net.mullvad.mullvadvpn.test.e2e.router.packetCapture.Stream
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/NetworkingProtocol.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/NetworkingProtocol.kt
new file mode 100644
index 0000000000..7cc12a9250
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/NetworkingProtocol.kt
@@ -0,0 +1,11 @@
+package net.mullvad.mullvadvpn.test.e2e.router
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+enum class NetworkingProtocol {
+ @SerialName("tcp") TCP,
+ @SerialName("udp") UDP,
+ @SerialName("icmp") ICMP,
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/DropRule.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/DropRule.kt
new file mode 100644
index 0000000000..627ecdc0fe
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/DropRule.kt
@@ -0,0 +1,26 @@
+package net.mullvad.mullvadvpn.test.e2e.router.firewall
+
+import android.annotation.SuppressLint
+import kotlinx.serialization.EncodeDefault
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import net.mullvad.mullvadvpn.test.e2e.misc.Networking
+import net.mullvad.mullvadvpn.test.e2e.router.NetworkingProtocol
+
+@SuppressLint("HardwareIds")
+@OptIn(ExperimentalSerializationApi::class)
+@Serializable
+data class DropRule(
+ @SerialName("src") val source: String,
+ @SerialName("dst") val destination: String,
+ val protocols: List<NetworkingProtocol>,
+ @EncodeDefault val label: String = "urn:uuid:${SessionIdentifier.fromDeviceIdentifier()}",
+) {
+ companion object {
+ fun blockUDPTrafficRule(to: String): DropRule {
+ val testDeviceIpAddress = Networking.getDeviceIpv4Address()
+ return DropRule(testDeviceIpAddress, to, listOf(NetworkingProtocol.UDP))
+ }
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/FirewallClient.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/FirewallClient.kt
new file mode 100644
index 0000000000..fabfee0132
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/FirewallClient.kt
@@ -0,0 +1,73 @@
+package net.mullvad.mullvadvpn.test.e2e.router.firewall
+
+import co.touchlab.kermit.Logger
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.engine.cio.CIO
+import io.ktor.client.plugins.HttpResponseValidator
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.plugins.defaultRequest
+import io.ktor.client.request.delete
+import io.ktor.client.request.post
+import io.ktor.client.request.setBody
+import io.ktor.http.ContentType
+import io.ktor.http.contentType
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.contextual
+import net.mullvad.mullvadvpn.test.e2e.BuildConfig
+import net.mullvad.mullvadvpn.test.e2e.serializer.NanoSecondsTimestampSerializer
+import org.junit.jupiter.api.fail
+
+class FirewallClient(private val httpClient: HttpClient = defaultHttpClient()) {
+ suspend fun createRule(rule: DropRule) {
+ Logger.v(
+ "Sending create rule request with body: ${Json.encodeToString(DropRule.serializer(), rule)}"
+ )
+ Logger.v(
+ "Requesting firewall API to block ${rule.protocols} traffic from ${rule.source} to ${rule.destination}"
+ )
+ httpClient.post("rule") {
+ contentType(ContentType.Application.Json)
+ setBody(Json.encodeToString(DropRule.serializer(), rule))
+ }
+ }
+
+ suspend fun removeAllRules() {
+ Logger.v("Sending remove all rules request")
+ httpClient.delete("remove-rules/${SessionIdentifier.fromDeviceIdentifier()}")
+ }
+}
+
+private fun defaultHttpClient(): HttpClient =
+ HttpClient(CIO) {
+ defaultRequest { url("http://${BuildConfig.TEST_ROUTER_API_HOST}") }
+
+ install(ContentNegotiation) {
+ json(
+ Json {
+ isLenient = true
+ prettyPrint = true
+
+ serializersModule = SerializersModule {
+ contextual(NanoSecondsTimestampSerializer)
+ }
+ }
+ )
+ }
+
+ HttpResponseValidator {
+ validateResponse { response ->
+ val statusCode = response.status.value
+ if (statusCode >= 400) {
+ fail(
+ "Request failed with response status code $statusCode: ${response.body<String>()}"
+ )
+ }
+ }
+ handleResponseExceptionWithRequest { exception, _ ->
+ fail("Request failed to be sent with exception: ${exception.message}")
+ }
+ }
+ }
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/SessionIdentifier.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/SessionIdentifier.kt
new file mode 100644
index 0000000000..4838c36486
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/firewall/SessionIdentifier.kt
@@ -0,0 +1,28 @@
+package net.mullvad.mullvadvpn.test.e2e.router.firewall
+
+import android.annotation.SuppressLint
+import android.provider.Settings
+import androidx.test.platform.app.InstrumentationRegistry
+import java.util.UUID
+import kotlinx.serialization.Serializable
+
+@JvmInline
+@Serializable
+value class SessionIdentifier(val value: String) {
+ override fun toString(): String = value
+
+ companion object {
+ @SuppressLint("HardwareIds")
+ fun fromDeviceIdentifier(): SessionIdentifier {
+ val deviceIdentifier =
+ Settings.Secure.getString(
+ InstrumentationRegistry.getInstrumentation().targetContext.contentResolver,
+ Settings.Secure.ANDROID_ID,
+ )
+
+ return SessionIdentifier(
+ UUID.nameUUIDFromBytes(deviceIdentifier.toByteArray()).toString()
+ )
+ }
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Host.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Host.kt
index d59e15d017..0a7a2a5605 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Host.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Host.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.test.e2e.model
+package net.mullvad.mullvadvpn.test.e2e.router.packetCapture
data class Host(val ipAddress: String, val port: Int) {
companion object {
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Packet.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Packet.kt
index df5c5a57b9..5c788e5710 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Packet.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Packet.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.test.e2e.model
+package net.mullvad.mullvadvpn.test.e2e.router.packetCapture
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/PacketCapture.kt
index aa167c55b4..7854170114 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/PacketCapture.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.test.e2e.misc
+package net.mullvad.mullvadvpn.test.e2e.router.packetCapture
import co.touchlab.kermit.Logger
import io.ktor.client.HttpClient
@@ -23,7 +23,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import net.mullvad.mullvadvpn.test.e2e.BuildConfig
-import net.mullvad.mullvadvpn.test.e2e.model.Stream
+import net.mullvad.mullvadvpn.test.e2e.misc.Networking
import net.mullvad.mullvadvpn.test.e2e.serializer.NanoSecondsTimestampSerializer
import net.mullvad.mullvadvpn.test.e2e.serializer.PacketCaptureSessionSerializer
import org.junit.jupiter.api.fail
@@ -63,7 +63,7 @@ class PacketCapture {
private fun defaultHttpClient(): HttpClient =
HttpClient(CIO) {
- defaultRequest { url("http://${BuildConfig.PACKET_CAPTURE_API_HOST}") }
+ defaultRequest { url("http://${BuildConfig.TEST_ROUTER_API_HOST}") }
install(ContentNegotiation) {
json(
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Stream.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Stream.kt
index 38feff34d6..564d0e25cb 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/model/Stream.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Stream.kt
@@ -1,8 +1,9 @@
-package net.mullvad.mullvadvpn.test.e2e.model
+package net.mullvad.mullvadvpn.test.e2e.router.packetCapture
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
+import net.mullvad.mullvadvpn.test.e2e.router.NetworkingProtocol
import org.joda.time.DateTime
import org.joda.time.Interval
@@ -11,7 +12,7 @@ data class Stream(
@SerialName("peer_addr") private val sourceAddressAndPort: String,
@SerialName("other_addr") private val destinationAddressAndPort: String,
@SerialName("flow_id") val flowId: String?,
- @SerialName("transport_protocol") val transportProtocol: NetworkTransportProtocol,
+ @SerialName("transport_protocol") val transportProtocol: NetworkingProtocol,
val packets: List<Packet>,
) {
@Transient val sourceHost = Host.fromString(sourceAddressAndPort)
@@ -40,10 +41,3 @@ data class Stream(
require(packets.isNotEmpty()) { "Stream must contain at least one packet" }
}
}
-
-@Serializable
-enum class NetworkTransportProtocol {
- @SerialName("tcp") TCP,
- @SerialName("udp") UDP,
- @SerialName("icmp") ICMP,
-}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketCaptureSessionSerializer.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketCaptureSessionSerializer.kt
index 8ec1a8bed9..16824af178 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketCaptureSessionSerializer.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketCaptureSessionSerializer.kt
@@ -6,7 +6,7 @@ import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
-import net.mullvad.mullvadvpn.test.e2e.misc.PacketCaptureSession
+import net.mullvad.mullvadvpn.test.e2e.router.packetCapture.PacketCaptureSession
object PacketCaptureSessionSerializer : KSerializer<PacketCaptureSession> {
override val descriptor: SerialDescriptor = String.serializer().descriptor
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketSerializer.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketSerializer.kt
index 60391218b4..b3617a6b65 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketSerializer.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/PacketSerializer.kt
@@ -6,9 +6,9 @@ import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
-import net.mullvad.mullvadvpn.test.e2e.model.Packet
-import net.mullvad.mullvadvpn.test.e2e.model.RxPacket
-import net.mullvad.mullvadvpn.test.e2e.model.TxPacket
+import net.mullvad.mullvadvpn.test.e2e.router.packetCapture.Packet
+import net.mullvad.mullvadvpn.test.e2e.router.packetCapture.RxPacket
+import net.mullvad.mullvadvpn.test.e2e.router.packetCapture.TxPacket
object PacketSerializer : JsonContentPolymorphicSerializer<Packet>(Packet::class) {
override fun selectDeserializer(element: JsonElement): KSerializer<out Packet> {