Skip to content

Commit 9d674a4

Browse files
authored
VPN: Add support for custom DNS (#4637)
Task/Issue URL: https://app.asana.com/0/488551667048375/1207500898026382/f ### Description See attached task description ### Steps to test this PR https://app.asana.com/0/0/1207531536629227/f
1 parent 4865e5a commit 9d674a4

File tree

36 files changed

+801
-618
lines changed

36 files changed

+801
-618
lines changed

common/common-utils/src/main/java/com/duckduckgo/common/utils/extensions/ActivityExtensions.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ fun AppCompatActivity.launchAlwaysOnSystemSettings() {
4444
startActivity(intent)
4545
}
4646

47+
@SuppressLint("InlinedApi")
48+
fun AppCompatActivity.launchSettings() {
49+
val intent = Intent(Settings.ACTION_SETTINGS)
50+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
51+
startActivity(intent)
52+
}
53+
4754
/**
4855
* Deep links to the battery optimization settings
4956
* @return `true` if it was able to deep link, otherwise `false`

network-protection/network-protection-impl/src/main/AndroidManifest.xml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,17 @@
3535
android:label="@string/netpVpnSettingsTitle"
3636
android:parentActivityName=".management.NetworkProtectionManagementActivity"
3737
android:exported="false" />
38-
<activity
39-
android:name=".settings.NetPNotificationSettingsActivity"
40-
android:label="@string/netpVpnNotificationSettingsTitle"
41-
android:parentActivityName=".management.NetworkProtectionManagementActivity"
42-
android:exported="false" />
4338
<activity
4439
android:name=".settings.geoswitching.NetpGeoswitchingActivity"
4540
android:label="@string/netpGeoswitchingTitle"
4641
android:parentActivityName="settings.NetPVpnSettingsActivity"
4742
android:exported="false" />
43+
44+
<activity
45+
android:name=".settings.custom_dns.VpnCustomDnsActivity"
46+
android:label="@string/netpCustomDnsSettingText"
47+
android:exported="false" />
48+
4849
<receiver
4950
android:name=".notification.NetPEnableReceiver"
5051
android:exported="false"

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/WgVpnNetworkStack.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class WgVpnNetworkStack @Inject constructor(
8080
// why? no use intercepting encrypted DNS traffic, plus we can't configure any DNS that doesn't support DoT, otherwise Android
8181
// will enforce DoT and will stop passing any DNS traffic, resulting in no DNS resolution == connectivity is killed
8282
dns = if (privateDns.isEmpty()) wgConfig!!.`interface`.dnsServers else emptySet(),
83-
customDns = netPDefaultConfigProvider.fallbackDns(),
83+
customDns = if (privateDns.isEmpty()) netPDefaultConfigProvider.fallbackDns() else emptySet(),
8484
routes = wgConfig!!.`interface`.routes.associate { it.address.hostAddress!! to it.mask },
8585
appExclusionList = wgConfig!!.`interface`.excludedApplications,
8686
),

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/config/NetPDefaultConfigProvider.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.duckduckgo.common.utils.DispatcherProvider
2020
import com.duckduckgo.di.scopes.VpnScope
2121
import com.duckduckgo.networkprotection.impl.exclusion.systemapps.SystemAppsExclusionRepository
2222
import com.duckduckgo.networkprotection.impl.settings.NetPSettingsLocalConfig
23+
import com.duckduckgo.networkprotection.impl.settings.NetpVpnSettingsDataStore
2324
import com.duckduckgo.networkprotection.store.NetPExclusionListRepository
2425
import com.squareup.anvil.annotations.ContributesBinding
2526
import java.net.Inet4Address
@@ -39,14 +40,19 @@ interface NetPDefaultConfigProvider {
3940
fun pcapConfig(): PcapConfig? = null
4041
}
4142

42-
data class PcapConfig(val filename: String, val snapLen: Int, val fileSize: Int)
43+
data class PcapConfig(
44+
val filename: String,
45+
val snapLen: Int,
46+
val fileSize: Int,
47+
)
4348

4449
@ContributesBinding(VpnScope::class)
4550
class RealNetPDefaultConfigProvider @Inject constructor(
4651
private val netPExclusionListRepository: NetPExclusionListRepository,
4752
private val dispatcherProvider: DispatcherProvider,
4853
private val netPSettingsLocalConfig: NetPSettingsLocalConfig,
4954
private val systemAppsExclusionRepository: SystemAppsExclusionRepository,
55+
private val netpVpnSettingsDataStore: NetpVpnSettingsDataStore,
5056
) : NetPDefaultConfigProvider {
5157
override suspend fun exclusionList(): Set<String> {
5258
return mutableSetOf<String>().apply {
@@ -70,4 +76,12 @@ class RealNetPDefaultConfigProvider @Inject constructor(
7076
}
7177
}
7278
}
79+
80+
override fun fallbackDns(): Set<InetAddress> {
81+
return netpVpnSettingsDataStore.customDns?.run {
82+
runCatching {
83+
InetAddress.getAllByName(this).toSet()
84+
}.getOrDefault(emptySet())
85+
} ?: emptySet()
86+
}
7387
}

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/integration/NetPStateCollector.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.duckduckgo.networkprotection.impl.connectionclass.ConnectionQualitySt
2727
import com.duckduckgo.networkprotection.impl.connectionclass.asConnectionQuality
2828
import com.duckduckgo.networkprotection.impl.exclusion.systemapps.SystemAppsExclusionRepository
2929
import com.duckduckgo.networkprotection.impl.settings.NetPSettingsLocalConfig
30+
import com.duckduckgo.networkprotection.impl.settings.NetpVpnSettingsDataStore
3031
import com.duckduckgo.networkprotection.impl.subscription.NetpSubscriptionManager
3132
import com.duckduckgo.networkprotection.impl.subscription.isActive
3233
import com.duckduckgo.networkprotection.store.NetPExclusionListRepository
@@ -47,6 +48,7 @@ class NetPStateCollector @Inject constructor(
4748
private val netPGeoswitchingRepository: NetPGeoswitchingRepository,
4849
private val netpSubscriptionManager: NetpSubscriptionManager,
4950
private val systemAppsExclusionRepository: SystemAppsExclusionRepository,
51+
private val netpVpnSettingsDataStore: NetpVpnSettingsDataStore,
5052
) : VpnStateCollectorPlugin {
5153

5254
override suspend fun collectVpnRelatedState(appPackageId: String?): JSONObject {
@@ -67,6 +69,7 @@ class NetPStateCollector @Inject constructor(
6769
put("excludeLocalNetworks", netPSettingsLocalConfig.vpnExcludeLocalNetworkRoutes().isEnabled())
6870
put("excludedSystemAppCategories", systemAppsExclusionRepository.getExcludedCategories().map { it.name })
6971
put("pauseDuringWifiCallsEnabled", netPSettingsLocalConfig.vpnPauseDuringCalls().isEnabled())
72+
put("customDNS", !netpVpnSettingsDataStore.customDns.isNullOrEmpty())
7073
}
7174
}
7275
}

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/management/NetworkProtectionManagementActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,19 @@ class NetworkProtectionManagementActivity : DuckDuckGoActivity() {
244244
if (connectionDetailsData.ipAddress.isNullOrEmpty()) {
245245
connectionDetails.connectionDetailsIp.gone()
246246
} else {
247+
connectionDetails.connectionDetailsIp.show()
247248
connectionDetails.connectionDetailsIp.setSecondaryText(connectionDetailsData.ipAddress)
248249
}
249250

250251
connectionDetails.transmittedText.text = formatFileSize(applicationContext, connectionDetailsData.transmittedData)
251252
connectionDetails.receivedText.text = formatFileSize(applicationContext, connectionDetailsData.receivedData)
253+
254+
if (connectionDetailsData.customDns.isNullOrEmpty()) {
255+
connectionDetails.connectionDetailsDns.gone()
256+
} else {
257+
connectionDetails.connectionDetailsDns.show()
258+
connectionDetails.connectionDetailsDns.setSecondaryText(connectionDetailsData.customDns)
259+
}
252260
}
253261

254262
private fun ActivityNetpManagementBinding.renderDisconnectedState() {

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/management/NetworkProtectionManagementViewModel.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import com.duckduckgo.networkprotection.impl.management.NetworkProtectionManagem
5757
import com.duckduckgo.networkprotection.impl.management.NetworkProtectionManagementViewModel.ConnectionState.Disconnected
5858
import com.duckduckgo.networkprotection.impl.management.NetworkProtectionManagementViewModel.ConnectionState.Unknown
5959
import com.duckduckgo.networkprotection.impl.pixels.NetworkProtectionPixels
60+
import com.duckduckgo.networkprotection.impl.settings.NetpVpnSettingsDataStore
6061
import com.duckduckgo.networkprotection.impl.settings.geoswitching.getDisplayableCountry
6162
import com.duckduckgo.networkprotection.impl.settings.geoswitching.getEmojiForCountryCode
6263
import com.duckduckgo.networkprotection.impl.store.NetworkProtectionRepository
@@ -87,6 +88,7 @@ class NetworkProtectionManagementViewModel @Inject constructor(
8788
private val netPGeoswitchingRepository: NetPGeoswitchingRepository,
8889
private val netpDataVolumeStore: NetpDataVolumeStore,
8990
private val netPExclusionListRepository: NetPExclusionListRepository,
91+
private val netpVpnSettingsDataStore: NetpVpnSettingsDataStore,
9092
) : ViewModel(), DefaultLifecycleObserver {
9193

9294
private val refreshVpnRunningState = MutableStateFlow(System.currentTimeMillis())
@@ -219,11 +221,13 @@ class NetworkProtectionManagementViewModel @Inject constructor(
219221
ConnectionDetails(
220222
location = serverDetails.location,
221223
ipAddress = serverDetails.ipAddress,
224+
customDns = netpVpnSettingsDataStore.customDns,
222225
)
223226
} else {
224227
connectionDetailsFlow.value!!.copy(
225228
location = serverDetails.location,
226229
ipAddress = serverDetails.ipAddress,
230+
customDns = netpVpnSettingsDataStore.customDns,
227231
)
228232
}
229233
}
@@ -246,12 +250,14 @@ class NetworkProtectionManagementViewModel @Inject constructor(
246250
elapsedConnectedTime = getElapsedTimeString(enabledTime),
247251
transmittedData = dataVolume.transmittedBytes,
248252
receivedData = dataVolume.receivedBytes,
253+
customDns = netpVpnSettingsDataStore.customDns,
249254
)
250255
} else {
251256
connectionDetailsFlow.value!!.copy(
252257
elapsedConnectedTime = getElapsedTimeString(enabledTime),
253258
transmittedData = dataVolume.transmittedBytes,
254259
receivedData = dataVolume.receivedBytes,
260+
customDns = netpVpnSettingsDataStore.customDns,
255261
)
256262
}
257263
}
@@ -406,6 +412,7 @@ class NetworkProtectionManagementViewModel @Inject constructor(
406412
val elapsedConnectedTime: String? = null,
407413
val transmittedData: Long = 0L,
408414
val receivedData: Long = 0L,
415+
val customDns: String? = null,
409416
)
410417

411418
enum class ConnectionState {

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/pixels/NetworkProtectionPixelNames.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,8 @@ enum class NetworkProtectionPixelNames(
140140
NETP_SERVER_MIGRATION_ATTEMPT_SUCCESS_DAILY("m_netp_ev_server_migration_attempt_success_d", enqueue = true),
141141
NETP_SERVER_MIGRATION_ATTEMPT_FAILED("m_netp_ev_server_migration_attempt_failed_c", enqueue = true),
142142
NETP_SERVER_MIGRATION_ATTEMPT_FAILED_DAILY("m_netp_ev_server_migration_attempt_failed_d", enqueue = true),
143+
NETP_UPDATE_CUSTOM_DNS("m_netp_ev_update_dns_custom_c", enqueue = true),
144+
NETP_UPDATE_CUSTOM_DNS_DAILY("m_netp_ev_update_dns_custom_d", enqueue = true),
145+
NETP_UPDATE_DEFAULT_DNS("m_netp_ev_update_dns_default_c", enqueue = true),
146+
NETP_UPDATE_DEFAULT_DNS_DAILY("m_netp_ev_update_dns_default_d", enqueue = true),
143147
}

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/pixels/NetworkProtectionPixels.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ interface NetworkProtectionPixels {
286286
fun reportServerMigrationAttempt()
287287
fun reportServerMigrationAttemptSuccess()
288288
fun reportServerMigrationAttemptFailed()
289+
290+
fun reportCustomDnsSet()
291+
292+
fun reportDefaultDnsSet()
289293
}
290294

291295
@ContributesBinding(AppScope::class)
@@ -594,6 +598,16 @@ class RealNetworkProtectionPixel @Inject constructor(
594598
tryToFireDailyPixel(NETP_SERVER_MIGRATION_ATTEMPT_FAILED_DAILY)
595599
}
596600

601+
override fun reportCustomDnsSet() {
602+
firePixel(NETP_UPDATE_CUSTOM_DNS)
603+
tryToFireDailyPixel(NETP_UPDATE_CUSTOM_DNS_DAILY)
604+
}
605+
606+
override fun reportDefaultDnsSet() {
607+
firePixel(NETP_UPDATE_DEFAULT_DNS)
608+
tryToFireDailyPixel(NETP_UPDATE_DEFAULT_DNS_DAILY)
609+
}
610+
597611
private fun firePixel(
598612
p: NetworkProtectionPixelNames,
599613
payload: Map<String, String> = emptyMap(),

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/settings/NetPNotificationSettingsActivity.kt

Lines changed: 0 additions & 82 deletions
This file was deleted.

0 commit comments

Comments
 (0)