Skip to content

Commit 104c590

Browse files
committed
Route VPN controller outside VPN
1 parent c10d735 commit 104c590

File tree

18 files changed

+447
-133
lines changed

18 files changed

+447
-133
lines changed

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

+3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ interface VpnRemoteFeatures {
5151

5252
@DefaultValue(false)
5353
fun allowDnsBlockMalware(): Toggle
54+
55+
@DefaultValue(false)
56+
fun localDNS(): Toggle
5457
}
5558

5659
@ContributesBinding(AppScope::class)

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

+112-8
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ internal class WgVpnRoutes {
3131
* - link-local address range
3232
* - multicast (Class D) address range - 224.0.0.0 to 239.255.255.255
3333
* - broadcast address
34+
* - eun controller: 20.93.77.32
35+
* - use controller: 20.253.26.112
3436
*/
3537
companion object {
3638
val wgVpnDefaultRoutes: Map<String, Int> = mapOf(
@@ -39,7 +41,58 @@ internal class WgVpnRoutes {
3941
// Excluded range: 10.0.0.0 -> 10.255.255.255
4042
"11.0.0.0" to 8,
4143
"12.0.0.0" to 6,
42-
"16.0.0.0" to 4,
44+
"16.0.0.0" to 6,
45+
"20.0.0.0" to 10,
46+
"20.64.0.0" to 12,
47+
"20.80.0.0" to 13,
48+
"20.88.0.0" to 14,
49+
"20.92.0.0" to 16,
50+
"20.93.0.0" to 18,
51+
"20.93.64.0" to 21,
52+
"20.93.72.0" to 22,
53+
"20.93.76.0" to 24,
54+
"20.93.77.0" to 27,
55+
// Excluded range: 20.93.77.32 -> 20.93.77.32
56+
"20.93.77.33" to 32,
57+
"20.93.77.34" to 31,
58+
"20.93.77.36" to 30,
59+
"20.93.77.40" to 29,
60+
"20.93.77.48" to 28,
61+
"20.93.77.64" to 26,
62+
"20.93.77.128" to 25,
63+
"20.93.78.0" to 23,
64+
"20.93.80.0" to 20,
65+
"20.93.96.0" to 19,
66+
"20.93.128.0" to 17,
67+
"20.94.0.0" to 15,
68+
"20.96.0.0" to 11,
69+
"20.128.0.0" to 10,
70+
"20.192.0.0" to 11,
71+
"20.224.0.0" to 12,
72+
"20.240.0.0" to 13,
73+
"20.248.0.0" to 14,
74+
"20.252.0.0" to 16,
75+
"20.253.0.0" to 20,
76+
"20.253.16.0" to 21,
77+
"20.253.24.0" to 23,
78+
"20.253.26.0" to 26,
79+
"20.253.26.64" to 27,
80+
"20.253.26.96" to 28,
81+
// Excluded range: 20.253.26.112 -> 20.253.26.112
82+
"20.253.26.113" to 32,
83+
"20.253.26.114" to 31,
84+
"20.253.26.116" to 30,
85+
"20.253.26.120" to 29,
86+
"20.253.26.128" to 25,
87+
"20.253.27.0" to 24,
88+
"20.253.28.0" to 22,
89+
"20.253.32.0" to 19,
90+
"20.253.64.0" to 18,
91+
"20.253.128.0" to 17,
92+
"20.254.0.0" to 15,
93+
"21.0.0.0" to 8,
94+
"22.0.0.0" to 7,
95+
"24.0.0.0" to 5,
4396
"32.0.0.0" to 3,
4497
"64.0.0.0" to 3,
4598
"96.0.0.0" to 4,
@@ -62,7 +115,11 @@ internal class WgVpnRoutes {
62115
"169.255.0.0" to 16,
63116
"170.0.0.0" to 7,
64117
"172.0.0.0" to 12,
65-
// Excluded range: 172.16.0.0 -> 172.31.255.255
118+
// Excluded range: 172.16.0.0 -> 172.16.255.255
119+
"172.17.0.0" to 16,
120+
"172.18.0.0" to 15,
121+
"172.20.0.0" to 14,
122+
"172.24.0.0" to 13,
66123
"172.32.0.0" to 11,
67124
"172.64.0.0" to 10,
68125
"172.128.0.0" to 9,
@@ -88,12 +145,59 @@ internal class WgVpnRoutes {
88145
)
89146

90147
val wgVpnRoutesIncludingLocal: Map<String, Int> = mapOf(
91-
"0.0.0.0" to 5,
92-
"8.0.0.0" to 7,
93-
"10.0.0.0" to 8,
94-
"11.0.0.0" to 8,
95-
"12.0.0.0" to 6,
96-
"16.0.0.0" to 4,
148+
"0.0.0.0" to 4,
149+
"16.0.0.0" to 6,
150+
"20.0.0.0" to 10,
151+
"20.64.0.0" to 12,
152+
"20.80.0.0" to 13,
153+
"20.88.0.0" to 14,
154+
"20.92.0.0" to 16,
155+
"20.93.0.0" to 18,
156+
"20.93.64.0" to 21,
157+
"20.93.72.0" to 22,
158+
"20.93.76.0" to 24,
159+
"20.93.77.0" to 27,
160+
// Excluded range: 20.93.77.32 -> 20.93.77.32
161+
"20.93.77.33" to 32,
162+
"20.93.77.34" to 31,
163+
"20.93.77.36" to 30,
164+
"20.93.77.40" to 29,
165+
"20.93.77.48" to 28,
166+
"20.93.77.64" to 26,
167+
"20.93.77.128" to 25,
168+
"20.93.78.0" to 23,
169+
"20.93.80.0" to 20,
170+
"20.93.96.0" to 19,
171+
"20.93.128.0" to 17,
172+
"20.94.0.0" to 15,
173+
"20.96.0.0" to 11,
174+
"20.128.0.0" to 10,
175+
"20.192.0.0" to 11,
176+
"20.224.0.0" to 12,
177+
"20.240.0.0" to 13,
178+
"20.248.0.0" to 14,
179+
"20.252.0.0" to 16,
180+
"20.253.0.0" to 20,
181+
"20.253.16.0" to 21,
182+
"20.253.24.0" to 23,
183+
"20.253.26.0" to 26,
184+
"20.253.26.64" to 27,
185+
"20.253.26.96" to 28,
186+
// Excluded range: 20.253.26.112 -> 20.253.26.112
187+
"20.253.26.113" to 32,
188+
"20.253.26.114" to 31,
189+
"20.253.26.116" to 30,
190+
"20.253.26.120" to 29,
191+
"20.253.26.128" to 25,
192+
"20.253.27.0" to 24,
193+
"20.253.28.0" to 22,
194+
"20.253.32.0" to 19,
195+
"20.253.64.0" to 18,
196+
"20.253.128.0" to 17,
197+
"20.254.0.0" to 15,
198+
"21.0.0.0" to 8,
199+
"22.0.0.0" to 7,
200+
"24.0.0.0" to 5,
97201
"32.0.0.0" to 3,
98202
"64.0.0.0" to 3,
99203
"96.0.0.0" to 4,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.networkprotection.impl.configuration
18+
19+
import com.duckduckgo.di.scopes.VpnScope
20+
import com.duckduckgo.networkprotection.impl.VpnRemoteFeatures
21+
import com.squareup.anvil.annotations.ContributesTo
22+
import com.squareup.moshi.Moshi
23+
import dagger.Module
24+
import dagger.Provides
25+
import dagger.SingleInstanceIn
26+
import java.net.InetAddress
27+
import javax.inject.Qualifier
28+
import logcat.asLog
29+
import logcat.logcat
30+
import okhttp3.Dns
31+
32+
interface VpnLocalDns : Dns
33+
34+
private class VpnLocalDnsImpl(
35+
private val vpnRemoteFeatures: VpnRemoteFeatures,
36+
moshi: Moshi,
37+
private val defaultDns: Dns,
38+
) : VpnLocalDns {
39+
40+
private val adapter = moshi.adapter(LocalDnsSettings::class.java)
41+
42+
/**
43+
* String is the domain
44+
* DnsEntry is the DNS entry corresponding to the domain
45+
*/
46+
private val localDomains: Map<String, List<DnsEntry>> by lazy {
47+
getRemoteDnsEntries()
48+
}
49+
50+
private val fallbackDomains: Map<String, List<DnsEntry>> = mapOf(
51+
"controller.netp.duckduckgo.com" to listOf(
52+
DnsEntry("20.253.26.112", "use"),
53+
DnsEntry("20.93.77.32", "eun"),
54+
),
55+
)
56+
57+
override fun lookup(hostname: String): List<InetAddress> {
58+
logcat { "Lookup for $hostname" }
59+
60+
val localResolution = localDomains[hostname] ?: fallbackDomains[hostname]
61+
62+
return localResolution?.let { entries ->
63+
logcat { "Hardcoded DNS for $hostname" }
64+
65+
entries.map { InetAddress.getByName(it.address) }
66+
} ?: defaultDns.lookup(hostname)
67+
}
68+
69+
private fun getRemoteDnsEntries(): Map<String, List<DnsEntry>> {
70+
vpnRemoteFeatures.localDNS().getSettings()?.let { settings ->
71+
return try {
72+
adapter.fromJson(settings)?.domains
73+
} catch (t: Throwable) {
74+
logcat { "Error parsing localDNS settings: ${t.asLog()}" }
75+
null
76+
}.orEmpty()
77+
}
78+
79+
return emptyMap()
80+
}
81+
82+
/*
83+
"settings": {
84+
"domains": {
85+
"controller.duckduckgo.com": [
86+
{
87+
"address": "1.2.3.4",
88+
"region": "use"
89+
},
90+
{
91+
"address": "1.2.2.2",
92+
"region": "eun"
93+
}
94+
]
95+
}
96+
}
97+
*/
98+
99+
private data class LocalDnsSettings(
100+
val domains: Map<String, List<DnsEntry>>,
101+
)
102+
103+
private data class DnsEntry(
104+
val address: String,
105+
val region: String,
106+
)
107+
}
108+
109+
@ContributesTo(VpnScope::class)
110+
@Module
111+
object VpnLocalDnsModule {
112+
@Retention(AnnotationRetention.BINARY)
113+
@Qualifier
114+
private annotation class InternalApi
115+
116+
@Provides
117+
@SingleInstanceIn(VpnScope::class)
118+
fun provideVpnLocalDns(
119+
vpnRemoteFeatures: VpnRemoteFeatures,
120+
moshi: Moshi,
121+
@InternalApi defaultDns: Dns,
122+
): VpnLocalDns {
123+
return VpnLocalDnsImpl(vpnRemoteFeatures, moshi, defaultDns)
124+
}
125+
126+
@Provides
127+
@InternalApi
128+
fun provideDefaultDns(): Dns {
129+
return Dns.SYSTEM
130+
}
131+
}

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import com.duckduckgo.di.scopes.VpnScope
2323
import com.duckduckgo.networkprotection.impl.configuration.WgServerApi.Mode
2424
import com.duckduckgo.networkprotection.impl.configuration.WgServerApi.Mode.FailureRecovery
2525
import com.duckduckgo.networkprotection.impl.configuration.WgServerApi.WgServerData
26-
import com.duckduckgo.networkprotection.impl.di.UnprotectedVpnControllerService
2726
import com.duckduckgo.networkprotection.impl.settings.geoswitching.NetpEgressServersProvider
2827
import com.squareup.anvil.annotations.ContributesBinding
2928
import javax.inject.Inject
@@ -55,7 +54,7 @@ interface WgServerApi {
5554

5655
@ContributesBinding(VpnScope::class)
5756
class RealWgServerApi @Inject constructor(
58-
@UnprotectedVpnControllerService private val wgVpnControllerService: WgVpnControllerService,
57+
private val wgVpnControllerService: WgVpnControllerService,
5958
private val serverDebugProvider: WgServerDebugProvider,
6059
private val netNetpEgressServersProvider: NetpEgressServersProvider,
6160
private val appBuildConfig: AppBuildConfig,
@@ -83,7 +82,9 @@ class RealWgServerApi @Inject constructor(
8382
null
8483
}
8584

86-
val userPreferredLocation = netNetpEgressServersProvider.updateServerLocationsAndReturnPreferred()
85+
val userPreferredLocation = netNetpEgressServersProvider.updateServerLocationsAndReturnPreferred(
86+
wgVpnControllerService.getEligibleLocations(),
87+
)
8788
val registerKeyBody = if (mode is FailureRecovery) {
8889
RegisterKeyBody(publicKey = publicKey, server = mode.currentServer, mode = mode.toString())
8990
} else if (selectedServer != null) {

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import java.net.InetAddress
3838
import javax.inject.Inject
3939
import javax.inject.Qualifier
4040
import logcat.LogPriority
41+
import logcat.LogPriority.ERROR
4142
import logcat.asLog
4243
import logcat.logcat
4344

@@ -212,7 +213,11 @@ class RealWgTunnel @Inject constructor(
212213
null
213214
}
214215

215-
val serverData = wgServerApi.registerPublicKey(publicKey, mode = mode) ?: throw NullPointerException("serverData = null")
216+
val serverData = kotlin.runCatching {
217+
wgServerApi.registerPublicKey(publicKey, mode = mode) ?: throw NullPointerException("serverData = null")
218+
}.onFailure {
219+
logcat(ERROR) { "Error registering public key" }
220+
}.getOrThrow()
216221

217222
return Config.Builder()
218223
.setInterface(

0 commit comments

Comments
 (0)