diff --git a/android/app/src/main/java/com/masterdns/vpn/ui/settings/GlobalSettingsScreen.kt b/android/app/src/main/java/com/masterdns/vpn/ui/settings/GlobalSettingsScreen.kt index 56e0501..b085560 100644 --- a/android/app/src/main/java/com/masterdns/vpn/ui/settings/GlobalSettingsScreen.kt +++ b/android/app/src/main/java/com/masterdns/vpn/ui/settings/GlobalSettingsScreen.kt @@ -81,6 +81,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import androidx.compose.runtime.LaunchedEffect +import java.net.InetAddress @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -125,13 +126,35 @@ fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) { } return } + val customDnsServers = parseCsv(draft.customDnsServers) + val invalidDnsServers = customDnsServers.filterNot(::isValidIpLiteral) + if (invalidDnsServers.isNotEmpty()) { + scope.launch { + snackbarHostState.showSnackbar( + context.getString(R.string.global_custom_dns_invalid_msg, invalidDnsServers.joinToString(", ")) + ) + } + return + } + if (draft.internetSharingEnabled && + (draft.internetSharingUser.isBlank() || draft.internetSharingPass.isBlank()) + ) { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.global_sharing_credentials_required_msg)) + } + return + } val safeSocksPort = socksPortValue ?: return val safeHttpPort = httpPortValue ?: return - draft = draft.copy( + val sanitized = draft.copy( internetSharingSocksPort = safeSocksPort, - internetSharingHttpPort = safeHttpPort + internetSharingHttpPort = safeHttpPort, + customDnsServers = customDnsServers.joinToString(","), + internetSharingUser = draft.internetSharingUser.trim(), + internetSharingPass = draft.internetSharingPass.trim() ) - vm.save(normalize(draft)) + draft = sanitized + vm.save(normalize(sanitized)) scope.launch { snackbarHostState.showSnackbar(context.getString(R.string.global_settings_saved_msg)) } } @@ -319,6 +342,11 @@ fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) { color = MdvColor.PrimaryContainer ) } + Text( + stringResource(R.string.global_sharing_lan_warning), + style = MaterialTheme.typography.bodySmall, + color = MdvColor.Error + ) Row( modifier = Modifier.fillMaxWidth(), @@ -676,6 +704,9 @@ private fun parseCsv(value: String): Set { private fun normalize(settings: GlobalSettings): GlobalSettings { return settings.copy( connectionMode = settings.connectionMode.uppercase(), + customDnsServers = parseCsv(settings.customDnsServers).joinToString(","), + internetSharingUser = settings.internetSharingUser.trim(), + internetSharingPass = settings.internetSharingPass.trim(), splitPackagesCsv = settings.splitPackagesCsv .split(",") .map { it.trim() } @@ -685,6 +716,18 @@ private fun normalize(settings: GlobalSettings): GlobalSettings { ) } +private fun isValidIpLiteral(value: String): Boolean { + val text = value.trim() + if (text.isBlank()) return false + val numericCandidate = when { + "." in text && ":" !in text -> text.matches(Regex("\\d{1,3}(\\.\\d{1,3}){3}")) + ":" in text -> text.matches(Regex("[0-9A-Fa-f:.]+")) + else -> false + } + if (!numericCandidate) return false + return runCatching { InetAddress.getByName(text) }.isSuccess +} + private fun getSystemLocalIp(): String? { return try { val interfaces = java.net.NetworkInterface.getNetworkInterfaces() diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 64dc1e5..178a9a5 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -23,6 +23,8 @@ Global settings saved and applied Please enter both SOCKS5 and HTTP ports. Ports must be between 1025 and 65535. + Invalid custom DNS server: %1$s + Internet sharing requires both username and password. Save Global Settings Split Tunnel Apps %1$d selected apps - tap to choose @@ -123,6 +125,7 @@ HTTP port is required. Username Password + Sharing listens on your local network. Use a username and password before enabling it. Use these endpoints to share your VPN connection with other devices or apps on the same network. MasterDnsVPN Project overview and build details