From 9d55978063947d5865bb3fa4e0c2ebef78f78812 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Mon, 6 Nov 2023 08:14:27 +0000 Subject: epic18: Manage VPN services for Tor or Tracker control --- .../trackers/service/TrackersService.kt | 21 +-- .../service/TrackersServiceSupervisorImpl.kt | 73 ---------- .../service/TrackersSupervisorStandalone.kt | 79 +++++++++++ .../advancedprivacy/trackers/service/TunLooper.kt | 1 + .../usecases/VpnSupervisorUseCaseStandalone.kt | 154 +++++++++++++++++++++ 5 files changed, 241 insertions(+), 87 deletions(-) delete mode 100644 trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt create mode 100644 trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersSupervisorStandalone.kt create mode 100644 trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/usecases/VpnSupervisorUseCaseStandalone.kt (limited to 'trackersservicestandalone/src') diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt index 918977f..152a3e9 100644 --- a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt +++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersService.kt @@ -16,16 +16,15 @@ */ package foundation.e.advancedprivacy.trackers.service -import android.content.Context import android.content.Intent import android.net.VpnService import android.os.Build import android.os.ParcelFileDescriptor import foundation.e.advancedprivacy.core.utils.notificationBuilder -import foundation.e.advancedprivacy.domain.entities.FeatureServiceState +import foundation.e.advancedprivacy.domain.entities.FeatureState import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_TRACKER_FLAG import foundation.e.advancedprivacy.domain.entities.NotificationContent -import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersServiceSupervisor +import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersSupervisor import foundation.e.advancedprivacy.trackers.service.Config.DNS_SERVER_TO_CATCH_IPV4 import foundation.e.advancedprivacy.trackers.service.Config.DNS_SERVER_TO_CATCH_IPV6 import foundation.e.advancedprivacy.trackers.service.Config.SESSION_NAME @@ -39,18 +38,12 @@ import timber.log.Timber class TrackersService : VpnService() { companion object { var coroutineScope = CoroutineScope(Dispatchers.IO) - - fun start(context: Context) { - prepare(context) - val intent = Intent(context, TrackersService::class.java) - context.startService(intent) - } } private val networkDNSAddressRepository: NetworkDNSAddressRepository = get(NetworkDNSAddressRepository::class.java) - private val trackersServiceSupervisor: TrackersServiceSupervisorImpl = get( - TrackersServiceSupervisor::class.java - ) as TrackersServiceSupervisorImpl + private val trackersSupervisor: TrackersSupervisorStandalone = get( + TrackersSupervisor::class.java + ) as TrackersSupervisorStandalone private val notificationTrackerFlag: NotificationContent = get(NotificationContent::class.java, named("notificationTrackerFlag")) @@ -64,14 +57,14 @@ class TrackersService : VpnService() { content = notificationTrackerFlag ).build() ) - trackersServiceSupervisor.state.value = FeatureServiceState.ON + trackersSupervisor.mutableState.value = FeatureState.ON return START_STICKY } override fun onDestroy() { networkDNSAddressRepository.stop() - trackersServiceSupervisor.state.value = FeatureServiceState.OFF + trackersSupervisor.mutableState.value = FeatureState.OFF super.onDestroy() } diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt deleted file mode 100644 index e2a6692..0000000 --- a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersServiceSupervisorImpl.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2023 MURENA SAS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package foundation.e.advancedprivacy.trackers.service - -import android.content.Context -import android.content.Intent -import foundation.e.advancedprivacy.domain.entities.FeatureServiceState -import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersServiceSupervisor -import foundation.e.advancedprivacy.trackers.service.data.NetworkDNSAddressRepository -import foundation.e.advancedprivacy.trackers.service.data.RequestDNSRepository -import foundation.e.advancedprivacy.trackers.service.usecases.ResolveDNSUseCase -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.MutableStateFlow -import org.koin.core.module.dsl.bind -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module -import org.pcap4j.packet.DnsPacket -import java.util.function.Function - -class TrackersServiceSupervisorImpl( - private val context: Context, - private val resolveDNSUseCase: ResolveDNSUseCase -) : TrackersServiceSupervisor { - internal val state: MutableStateFlow = MutableStateFlow(FeatureServiceState.OFF) - - override fun start(): Boolean { - return if (!isRunning()) { - state.value = FeatureServiceState.STARTING - TrackersService.start(context) - true - } else false - } - - override fun stop(): Boolean { - return when (state.value) { - FeatureServiceState.ON -> { - state.value = FeatureServiceState.STOPPING - kotlin.runCatching { TrackersService.coroutineScope.cancel() } - context.stopService(Intent(context, TrackersService::class.java)) - true - } - else -> false - } - } - - override fun isRunning(): Boolean { - return state.value != FeatureServiceState.OFF - } - - override val dnsFilterForIpScrambling = Function { dnsRequest -> resolveDNSUseCase.shouldBlock(dnsRequest) } -} - -val trackerServiceModule = module { - singleOf(::NetworkDNSAddressRepository) - singleOf(::RequestDNSRepository) - singleOf(::ResolveDNSUseCase) - singleOf(::TunLooper) - singleOf(::TrackersServiceSupervisorImpl) { bind() } -} diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersSupervisorStandalone.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersSupervisorStandalone.kt new file mode 100644 index 0000000..ac06ced --- /dev/null +++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TrackersSupervisorStandalone.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package foundation.e.advancedprivacy.trackers.service + +import android.content.Context +import android.content.Intent +import foundation.e.advancedprivacy.domain.entities.FeatureState +import foundation.e.advancedprivacy.domain.usecases.VpnSupervisorUseCase +import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersSupervisor +import foundation.e.advancedprivacy.trackers.service.data.NetworkDNSAddressRepository +import foundation.e.advancedprivacy.trackers.service.data.RequestDNSRepository +import foundation.e.advancedprivacy.trackers.service.usecases.ResolveDNSUseCase +import foundation.e.advancedprivacy.trackers.service.usecases.VpnSupervisorUseCaseStandalone +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.pcap4j.packet.DnsPacket +import java.util.function.Function + +class TrackersSupervisorStandalone( + private val context: Context, + private val resolveDNSUseCase: ResolveDNSUseCase +) : TrackersSupervisor { + internal val mutableState: MutableStateFlow = MutableStateFlow(FeatureState.OFF) + override val state: StateFlow = mutableState + + override fun start(): Boolean { + return if (!isRunning()) { + mutableState.value = FeatureState.STARTING + val intent = Intent(context, TrackersService::class.java) + context.startService(intent) + true + } else false + } + + override fun stop(): Boolean { + return when (mutableState.value) { + FeatureState.ON -> { + mutableState.value = FeatureState.STOPPING + kotlin.runCatching { TrackersService.coroutineScope.cancel() } + context.stopService(Intent(context, TrackersService::class.java)) + true + } + else -> false + } + } + + override fun isRunning(): Boolean { + return state.value != FeatureState.OFF + } + + override val dnsFilterForIpScrambling = Function { dnsRequest -> resolveDNSUseCase.shouldBlock(dnsRequest) } +} + +val trackerServiceModule = module { + singleOf(::NetworkDNSAddressRepository) + singleOf(::RequestDNSRepository) + singleOf(::ResolveDNSUseCase) + singleOf(::TunLooper) + singleOf(::TrackersSupervisorStandalone) { bind() } + singleOf(::VpnSupervisorUseCaseStandalone) { bind() } +} diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TunLooper.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TunLooper.kt index 7813c67..bb349eb 100644 --- a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TunLooper.kt +++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/TunLooper.kt @@ -84,6 +84,7 @@ class TunLooper( } } } + closeStreams() } private suspend fun handleIpPacket(buffer: ByteArray, pLen: Int) { diff --git a/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/usecases/VpnSupervisorUseCaseStandalone.kt b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/usecases/VpnSupervisorUseCaseStandalone.kt new file mode 100644 index 0000000..683f886 --- /dev/null +++ b/trackersservicestandalone/src/main/java/foundation/e/advancedprivacy/trackers/service/usecases/VpnSupervisorUseCaseStandalone.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2023 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package foundation.e.advancedprivacy.trackers.service.usecases + +import foundation.e.advancedprivacy.domain.entities.FeatureState +import foundation.e.advancedprivacy.domain.entities.MainFeatures +import foundation.e.advancedprivacy.domain.entities.MainFeatures.IpScrambling +import foundation.e.advancedprivacy.domain.entities.MainFeatures.TrackersControl +import foundation.e.advancedprivacy.domain.repositories.LocalStateRepository +import foundation.e.advancedprivacy.domain.usecases.VpnSupervisorUseCase +import foundation.e.advancedprivacy.externalinterfaces.servicesupervisors.FeatureSupervisor +import foundation.e.advancedprivacy.ipscrambler.OrbotSupervisor +import foundation.e.advancedprivacy.trackers.domain.externalinterfaces.TrackersSupervisor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.launch + +class VpnSupervisorUseCaseStandalone( + private val localStateRepository: LocalStateRepository, + private val trackersSupervisor: TrackersSupervisor, + private val orbotSupervisor: OrbotSupervisor, + private val scope: CoroutineScope, +) : VpnSupervisorUseCase { + private var applySettingJob: Job? = null + + init { + listenSettings() + } + + override fun listenSettings() { + var previousBlockTrackers: Boolean? = null + + localStateRepository.blockTrackers.combine( + localStateRepository.ipScramblingSetting + ) { blockTrackers, hideIp -> + applySettingJob?.cancel() + applySettingJob = scope.launch { + when { + blockTrackers && !hideIp -> + launchVpnService(trackersSupervisor) + + !blockTrackers && !hideIp -> + stopAllServices() + + else -> { + if (blockTrackers && previousBlockTrackers != true) { + localStateRepository.emitStartVpnDisclaimer(IpScrambling()) + } + + launchVpnService(orbotSupervisor) + } + } + + previousBlockTrackers = blockTrackers + } + }.launchIn(scope) + } + + private fun stopAllServices() { + listOf(orbotSupervisor, trackersSupervisor).map { stopVpnService(it) } + } + + private fun stopVpnService(supervisor: FeatureSupervisor): FeatureSupervisor { + when (supervisor.state.value) { + FeatureState.ON, + FeatureState.STARTING -> + supervisor.stop() + + else -> {} + } + return supervisor + } + + private suspend fun launchVpnService(supervisor: FeatureSupervisor) { + stopVpnService(otherSupervisor(supervisor)).let { otherSupervisor -> + otherSupervisor.state.first { it == FeatureState.OFF } + } + + when (supervisor.state.value) { + FeatureState.STOPPING -> { + supervisor.state.first { it == FeatureState.OFF } + initiateStartVpnService(supervisor) + } + + FeatureState.OFF -> initiateStartVpnService(supervisor) + else -> {} + } + } + + private fun otherSupervisor(supervisor: FeatureSupervisor): FeatureSupervisor { + return when (supervisor) { + trackersSupervisor -> orbotSupervisor + else -> trackersSupervisor + } + } + + private fun getSupervisor(feature: MainFeatures): FeatureSupervisor { + return when (feature) { + is TrackersControl -> trackersSupervisor + else -> orbotSupervisor + } + } + + private suspend fun initiateStartVpnService(supervisor: FeatureSupervisor) { + val authorizeVpnIntent = orbotSupervisor.prepareAndroidVpn() + val feature = when (supervisor) { + trackersSupervisor -> TrackersControl(authorizeVpnIntent) + else -> IpScrambling(authorizeVpnIntent) + } + + if (authorizeVpnIntent == null) { + localStateRepository.emitStartVpnDisclaimer(feature) + startVpnService(feature) + } else { + localStateRepository.emitStartVpnDisclaimer(feature) + } + } + + override fun startVpnService(feature: MainFeatures) { + if (feature is IpScrambling) { + localStateRepository.internetPrivacyMode.value = FeatureState.STARTING + orbotSupervisor.setDNSFilter(trackersSupervisor.dnsFilterForIpScrambling) + } + + getSupervisor(feature).start() + } + + override fun cancelStartVpnService(feature: MainFeatures) { + when (feature) { + is IpScrambling -> + localStateRepository.setIpScramblingSetting(enabled = false) + is TrackersControl -> + trackersSupervisor.stop() + else -> {} + } + } +} -- cgit v1.2.1