Skip to content

Commit 8d5fe2b

Browse files
committed
Megre source code Volter-p2p in main
1 parent aa3ff81 commit 8d5fe2b

126 files changed

Lines changed: 9210 additions & 261 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,4 @@ sudo ./volter-client \
3535
--mtu 1420
3636
```
3737

38-
Windows (администратор, `wintun.dll` рядом с exe или из релиза):
39-
40-
## systemd
41-
42-
Пример юнита: [contrib/volter-client.service](contrib/volter-client.service).
38+
Windows (администратор, `wintun.dll` рядом с exe или из релиза):

android/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ local.properties
88

99
key.properties
1010
keystore.properties
11+
volter.defaults.properties
1112

1213

1314
.idea/

android/app/build.gradle.kts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import java.util.Properties
2+
13
plugins {
24
id("com.android.application")
35
id("org.jetbrains.kotlin.android")
@@ -18,6 +20,25 @@ fun volterVersionCode(): Int =
1820
?: (project.findProperty("ptera.versionCode") as String?)?.toIntOrNull()
1921
?: 1
2022

23+
val volterDefaultsFile = rootProject.file("volter.defaults.properties")
24+
val volterDefaults = Properties().apply {
25+
if (volterDefaultsFile.exists()) {
26+
volterDefaultsFile.inputStream().use { load(it) }
27+
}
28+
}
29+
30+
fun escVolterBuildString(s: String): String =
31+
s.replace("\\", "\\\\").replace("\"", "\\\"")
32+
33+
val volterMeshBootstrapPub = volterDefaults.getProperty("bootstrapPubKey", "").trim()
34+
val volterMeshDhtSecret = volterDefaults.getProperty("dhtRpcSecret", "").trim()
35+
val volterMeshRelayPeerId = volterDefaults.getProperty("relayPeerId", "").trim()
36+
val volterDhtUdpPort = volterDefaults.getProperty("dhtUdpPort", "4001").trim().toIntOrNull() ?: 4001
37+
val volterDhtFindEnabled =
38+
volterDefaults.getProperty("dhtFindEnabled", "true").trim().equals("true", ignoreCase = true)
39+
40+
val volterMeshDefaultsEnabled = volterMeshBootstrapPub.isNotEmpty()
41+
2142
val ciDebugKeystore = file("ci-debug.keystore")
2243
val ciKeystorePass =
2344
System.getenv("VOLTER_CI_KEYSTORE_PASS") ?: System.getenv("PTERA_CI_KEYSTORE_PASS")
@@ -36,6 +57,13 @@ android {
3657

3758
versionCode = volterVersionCode()
3859
versionName = volterVersionName()
60+
61+
buildConfigField("boolean", "VOLTER_MESH_DEFAULTS", "$volterMeshDefaultsEnabled")
62+
buildConfigField("String", "VOLTER_BOOTSTRAP_PUB_KEY", "\"${escVolterBuildString(volterMeshBootstrapPub)}\"")
63+
buildConfigField("String", "VOLTER_DHT_RPC_SECRET", "\"${escVolterBuildString(volterMeshDhtSecret)}\"")
64+
buildConfigField("String", "VOLTER_RELAY_PEER_ID", "\"${escVolterBuildString(volterMeshRelayPeerId)}\"")
65+
buildConfigField("int", "VOLTER_DHT_UDP_PORT", "$volterDhtUdpPort")
66+
buildConfigField("boolean", "VOLTER_DHT_FIND_ENABLED", "$volterDhtFindEnabled")
3967
}
4068

4169
signingConfigs {

android/app/src/main/java/dev/c0redev/volter/core/CoreBridge.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@ object CoreBridge {
133133
return QuicIPsResult(ips = out, error = err)
134134
}
135135

136+
fun meshStatus(): String {
137+
return try {
138+
val c = Class.forName("core.Core")
139+
val m = c.getMethod("meshStatus")
140+
m.invoke(null) as String
141+
} catch (e: Exception) {
142+
VolterLog.w("meshStatus: ${e.message}")
143+
"""{"error":"meshStatus unavailable: rebuild volter-core.aar (gomobile bind) or update native layer","detail":"${e.message}"}"""
144+
}
145+
}
146+
136147
private fun nullableErr(j: JSONObject, key: String): String? {
137148
if (!j.has(key) || j.isNull(key)) return null
138149
val s = j.optString(key, "")

android/app/src/main/java/dev/c0redev/volter/domain/model/ClientSettings.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ data class ClientSettings(
88
val proxyListen: String = "127.0.0.1:1080",
99
val ipv6Tunnel: Boolean = false,
1010
val dualTun: Boolean = true,
11+
val volterMesh: Boolean = true,
1112
val transportPreference: String = TRANSPORT_AUTO,
1213
) {
1314
fun toJson(): JSONObject {
@@ -17,6 +18,7 @@ data class ClientSettings(
1718
if (proxyListen.isNotBlank()) j.put("proxyListen", proxyListen)
1819
j.put("ipv6Tunnel", ipv6Tunnel)
1920
j.put("dualTun", dualTun)
21+
j.put("volterMesh", volterMesh)
2022
j.put("transportPreference", Companion.normalizedTransportPreference(transportPreference))
2123
return j
2224
}
@@ -43,6 +45,7 @@ data class ClientSettings(
4345
proxyListen = j.optString("proxyListen", "127.0.0.1:1080"),
4446
ipv6Tunnel = j.optBoolean("ipv6Tunnel", false),
4547
dualTun = j.optBoolean("dualTun", true),
48+
volterMesh = j.optBoolean("volterMesh", true),
4649
transportPreference = normalizedTransportPreference(j.optString("transportPreference", TRANSPORT_AUTO)),
4750
)
4851
}

android/app/src/main/java/dev/c0redev/volter/domain/model/Config.kt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ data class Config(
1818
val tunCIDR6: String? = null,
1919
val dualTransport: Boolean? = null,
2020
val protection: ProtectionOptions? = null,
21+
val relay: RelayOptions? = null,
2122
) {
2223
fun withCloudDefaults(serverMode: String, probeIPv6: Boolean): Config {
2324
val noPin = quicCertPinSHA256.isNullOrBlank()
@@ -82,6 +83,7 @@ data class Config(
8283
tunCIDR6?.let { j.put("tunCIDR6", it) }
8384
dualTransport?.let { j.put("dualTransport", it) }
8485
protection?.let { j.put("protection", it.toJson()) }
86+
relay?.let { j.put("relay", it.toJson()) }
8587
return j
8688
}
8789

@@ -132,6 +134,7 @@ data class Config(
132134
else -> j.optBoolean("dualTransport", true)
133135
},
134136
protection = j.optJSONObject("protection")?.let { ProtectionOptions.fromJson(it) },
137+
relay = j.optJSONObject("relay")?.let { RelayOptions.fromJson(it) },
135138
)
136139
}
137140

@@ -167,14 +170,14 @@ data class Config(
167170
fun parseShareUri(raw: String): Pair<String, Config>? {
168171
val cfg = parseVolterUriConfig(raw) ?: return null
169172
val name = parseVolterUriName(raw)?.ifBlank { "imported" } ?: "imported"
170-
return sanitizeName(name) to cfg.copy(protection = null)
173+
return sanitizeName(name) to cfg.copy(protection = null, relay = null)
171174
}
172175

173176
fun buildShareUri(name: String, cfg: Config): String {
174177
val payload = JSONObject()
175178
payload.put("v", 1)
176179
payload.put("n", sanitizeName(name))
177-
payload.put("c", cfg.copy(protection = null).toJson())
180+
payload.put("c", cfg.copy(protection = null, relay = null).toJson())
178181
val b = Base64.encodeToString(payload.toString().toByteArray(Charsets.UTF_8), Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)
179182
return "volter://$b"
180183
}
@@ -231,6 +234,27 @@ data class Config(
231234
return if (idx > 0) s.substring(0, idx) else s
232235
}
233236

237+
238+
fun tcpPortFromServer(server: String): Int? {
239+
val s = server.trim()
240+
if (s.startsWith("[")) {
241+
val end = s.indexOf(']')
242+
if (end <= 0 || end >= s.lastIndex || s[end + 1] != ':') return null
243+
return s.substring(end + 2).toIntOrNull()?.takeIf { it in 1..65535 }
244+
}
245+
val idx = s.lastIndexOf(':')
246+
if (idx <= 0 || idx == s.lastIndex) return null
247+
return s.substring(idx + 1).toIntOrNull()?.takeIf { it in 1..65535 }
248+
}
249+
250+
251+
fun tcpAuthorityForHttp(server: String): String? {
252+
val port = tcpPortFromServer(server) ?: return null
253+
val host = hostFromServer(server)
254+
val hostPart = if (host.contains(':')) "[$host]" else host
255+
return "$hostPart:$port"
256+
}
257+
234258
fun quicHostPort(host: String, port: Int): String {
235259
val h = host.trim().removePrefix("[").removeSuffix("]")
236260
return if (h.contains(":")) "[$h]:$port" else "$h:$port"

android/app/src/main/java/dev/c0redev/volter/domain/model/ProtectionOptions.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ data class ProtectionOptions(
1616
val magicSplit: String? = null,
1717
val junkStyle: String? = null,
1818
val flushPolicy: String? = null,
19-
/** tls_record | tls_ch_shape | smb1_shape | mc_frame | rotate */
2019
val preambleProfile: String? = null,
2120
val preambleRotate: Boolean = false,
2221
) {
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package dev.c0redev.volter.domain.model
2+
3+
import dev.c0redev.volter.json.optJSONArrayStringList
4+
import dev.c0redev.volter.json.optNullableBoolean
5+
import dev.c0redev.volter.json.optNullableInt
6+
import dev.c0redev.volter.json.optNullableString
7+
import dev.c0redev.volter.json.putStringListIfNonempty
8+
import org.json.JSONObject
9+
10+
11+
data class RelayOptions(
12+
val peerId: String? = null,
13+
val privateKey: String? = null,
14+
val allowedClasses: List<String>? = null,
15+
val maxConcurrent: Int? = null,
16+
val budgetKbps: Int? = null,
17+
val discoverySigned: String? = null,
18+
val discoveryURL: String? = null,
19+
val gossipEnabled: Boolean? = null,
20+
val bootstrapPubKey: String? = null,
21+
val emergencyPolicyURL: String? = null,
22+
val emergencyPolicyPubKey: String? = null,
23+
val pathAggressive: Boolean? = null,
24+
val pathCooldownMs: Int? = null,
25+
val stunServers: List<String>? = null,
26+
val turnUrls: List<String>? = null,
27+
val gossipPeers: List<String>? = null,
28+
val gossipIntervalSec: Int? = null,
29+
val gossipMaxAgeSec: Int? = null,
30+
val geoAllowCountries: List<String>? = null,
31+
val geoDenyCountries: List<String>? = null,
32+
val stakeMin: Int? = null,
33+
val peerPathFromDiscovery: Boolean? = null,
34+
val peerRelayUseQuic: Boolean? = null,
35+
val peerRelayUseUdp: Boolean? = null,
36+
val peerRelayUdpListen: String? = null,
37+
val peerRelayUdpAdvertise: String? = null,
38+
val peerQuicServerName: String? = null,
39+
val dhtFindUrls: List<String>? = null,
40+
val stakeRegistryURL: String? = null,
41+
val stakeRegistryPubKey: String? = null,
42+
val stakeReputationFile: String? = null,
43+
val stakeBonusHttpUrl: String? = null,
44+
val stakeMerkleFile: String? = null,
45+
val stakeMerkleRootUrl: String? = null,
46+
val dhtRpcListenUdp: String? = null,
47+
val dhtRpcSecret: String? = null,
48+
val dhtRpcSeedPeers: List<String>? = null,
49+
val dhtRpcIntervalSec: Int? = null,
50+
val dhtRpcFindK: Int? = null,
51+
val dhtIterativeRounds: Int? = null,
52+
val dhtIterativeAlpha: Int? = null,
53+
val dhtPublishSrflx: Boolean? = null,
54+
val symmetricNatHolePunch: Boolean? = null,
55+
) {
56+
fun toJson(): JSONObject {
57+
val j = JSONObject()
58+
peerId?.takeIf { it.isNotBlank() }?.let { j.put("peerId", it) }
59+
privateKey?.takeIf { it.isNotBlank() }?.let { j.put("privateKey", it) }
60+
j.putStringListIfNonempty("allowedClasses", allowedClasses)
61+
maxConcurrent?.takeIf { it != 0 }?.let { j.put("maxConcurrent", it) }
62+
budgetKbps?.takeIf { it != 0 }?.let { j.put("budgetKbps", it) }
63+
discoverySigned?.takeIf { it.isNotBlank() }?.let { j.put("discoverySigned", it) }
64+
discoveryURL?.takeIf { it.isNotBlank() }?.let { j.put("discoveryURL", it) }
65+
gossipEnabled?.let { j.put("gossipEnabled", it) }
66+
bootstrapPubKey?.takeIf { it.isNotBlank() }?.let { j.put("bootstrapPubKey", it) }
67+
emergencyPolicyURL?.takeIf { it.isNotBlank() }?.let { j.put("emergencyPolicyURL", it) }
68+
emergencyPolicyPubKey?.takeIf { it.isNotBlank() }?.let { j.put("emergencyPolicyPubKey", it) }
69+
pathAggressive?.let { j.put("pathAggressive", it) }
70+
pathCooldownMs?.takeIf { it != 0 }?.let { j.put("pathCooldownMs", it) }
71+
j.putStringListIfNonempty("stunServers", stunServers)
72+
j.putStringListIfNonempty("turnUrls", turnUrls)
73+
j.putStringListIfNonempty("gossipPeers", gossipPeers)
74+
gossipIntervalSec?.takeIf { it != 0 }?.let { j.put("gossipIntervalSec", it) }
75+
gossipMaxAgeSec?.takeIf { it != 0 }?.let { j.put("gossipMaxAgeSec", it) }
76+
j.putStringListIfNonempty("geoAllowCountries", geoAllowCountries)
77+
j.putStringListIfNonempty("geoDenyCountries", geoDenyCountries)
78+
stakeMin?.takeIf { it != 0 }?.let { j.put("stakeMin", it) }
79+
peerPathFromDiscovery?.let { j.put("peerPathFromDiscovery", it) }
80+
peerRelayUseQuic?.let { j.put("peerRelayUseQuic", it) }
81+
peerRelayUseUdp?.let { j.put("peerRelayUseUdp", it) }
82+
peerRelayUdpListen?.takeIf { it.isNotBlank() }?.let { j.put("peerRelayUdpListen", it) }
83+
peerRelayUdpAdvertise?.takeIf { it.isNotBlank() }?.let { j.put("peerRelayUdpAdvertise", it) }
84+
peerQuicServerName?.takeIf { it.isNotBlank() }?.let { j.put("peerQuicServerName", it) }
85+
j.putStringListIfNonempty("dhtFindUrls", dhtFindUrls)
86+
stakeRegistryURL?.takeIf { it.isNotBlank() }?.let { j.put("stakeRegistryURL", it) }
87+
stakeRegistryPubKey?.takeIf { it.isNotBlank() }?.let { j.put("stakeRegistryPubKey", it) }
88+
stakeReputationFile?.takeIf { it.isNotBlank() }?.let { j.put("stakeReputationFile", it) }
89+
stakeBonusHttpUrl?.takeIf { it.isNotBlank() }?.let { j.put("stakeBonusHttpUrl", it) }
90+
stakeMerkleFile?.takeIf { it.isNotBlank() }?.let { j.put("stakeMerkleFile", it) }
91+
stakeMerkleRootUrl?.takeIf { it.isNotBlank() }?.let { j.put("stakeMerkleRootUrl", it) }
92+
dhtRpcListenUdp?.takeIf { it.isNotBlank() }?.let { j.put("dhtRpcListenUdp", it) }
93+
dhtRpcSecret?.takeIf { it.isNotBlank() }?.let { j.put("dhtRpcSecret", it) }
94+
j.putStringListIfNonempty("dhtRpcSeedPeers", dhtRpcSeedPeers)
95+
dhtRpcIntervalSec?.takeIf { it != 0 }?.let { j.put("dhtRpcIntervalSec", it) }
96+
dhtRpcFindK?.takeIf { it != 0 }?.let { j.put("dhtRpcFindK", it) }
97+
dhtIterativeRounds?.takeIf { it != 0 }?.let { j.put("dhtIterativeRounds", it) }
98+
dhtIterativeAlpha?.takeIf { it != 0 }?.let { j.put("dhtIterativeAlpha", it) }
99+
dhtPublishSrflx?.let { j.put("dhtPublishSrflx", it) }
100+
symmetricNatHolePunch?.let { j.put("symmetricNatHolePunch", it) }
101+
return j
102+
}
103+
104+
companion object {
105+
fun fromJson(j: JSONObject): RelayOptions {
106+
fun list(key: String) = j.optJSONArrayStringList(key)
107+
return RelayOptions(
108+
peerId = j.optNullableString("peerId"),
109+
privateKey = j.optNullableString("privateKey"),
110+
allowedClasses = list("allowedClasses"),
111+
maxConcurrent = j.optNullableInt("maxConcurrent"),
112+
budgetKbps = j.optNullableInt("budgetKbps"),
113+
discoverySigned = j.optNullableString("discoverySigned"),
114+
discoveryURL = j.optNullableString("discoveryURL"),
115+
gossipEnabled = j.optNullableBoolean("gossipEnabled"),
116+
bootstrapPubKey = j.optNullableString("bootstrapPubKey"),
117+
emergencyPolicyURL = j.optNullableString("emergencyPolicyURL"),
118+
emergencyPolicyPubKey = j.optNullableString("emergencyPolicyPubKey"),
119+
pathAggressive = j.optNullableBoolean("pathAggressive"),
120+
pathCooldownMs = j.optNullableInt("pathCooldownMs"),
121+
stunServers = list("stunServers"),
122+
turnUrls = list("turnUrls"),
123+
gossipPeers = list("gossipPeers"),
124+
gossipIntervalSec = j.optNullableInt("gossipIntervalSec"),
125+
gossipMaxAgeSec = j.optNullableInt("gossipMaxAgeSec"),
126+
geoAllowCountries = list("geoAllowCountries"),
127+
geoDenyCountries = list("geoDenyCountries"),
128+
stakeMin = j.optNullableInt("stakeMin"),
129+
peerPathFromDiscovery = j.optNullableBoolean("peerPathFromDiscovery"),
130+
peerRelayUseQuic = j.optNullableBoolean("peerRelayUseQuic"),
131+
peerRelayUseUdp = j.optNullableBoolean("peerRelayUseUdp"),
132+
peerRelayUdpListen = j.optNullableString("peerRelayUdpListen"),
133+
peerRelayUdpAdvertise = j.optNullableString("peerRelayUdpAdvertise"),
134+
peerQuicServerName = j.optNullableString("peerQuicServerName"),
135+
dhtFindUrls = list("dhtFindUrls"),
136+
stakeRegistryURL = j.optNullableString("stakeRegistryURL"),
137+
stakeRegistryPubKey = j.optNullableString("stakeRegistryPubKey"),
138+
stakeReputationFile = j.optNullableString("stakeReputationFile"),
139+
stakeBonusHttpUrl = j.optNullableString("stakeBonusHttpUrl"),
140+
stakeMerkleFile = j.optNullableString("stakeMerkleFile"),
141+
stakeMerkleRootUrl = j.optNullableString("stakeMerkleRootUrl"),
142+
dhtRpcListenUdp = j.optNullableString("dhtRpcListenUdp"),
143+
dhtRpcSecret = j.optNullableString("dhtRpcSecret"),
144+
dhtRpcSeedPeers = list("dhtRpcSeedPeers"),
145+
dhtRpcIntervalSec = j.optNullableInt("dhtRpcIntervalSec"),
146+
dhtRpcFindK = j.optNullableInt("dhtRpcFindK"),
147+
dhtIterativeRounds = j.optNullableInt("dhtIterativeRounds"),
148+
dhtIterativeAlpha = j.optNullableInt("dhtIterativeAlpha"),
149+
dhtPublishSrflx = j.optNullableBoolean("dhtPublishSrflx"),
150+
symmetricNatHolePunch = j.optNullableBoolean("symmetricNatHolePunch"),
151+
)
152+
}
153+
}
154+
}
155+
156+
157+
fun RelayOptions.withUserOverlay(user: RelayOptions?): RelayOptions {
158+
if (user == null) return this
159+
return copy(
160+
peerId = user.peerId ?: peerId,
161+
privateKey = user.privateKey ?: privateKey,
162+
allowedClasses = user.allowedClasses ?: allowedClasses,
163+
maxConcurrent = user.maxConcurrent ?: maxConcurrent,
164+
budgetKbps = user.budgetKbps ?: budgetKbps,
165+
discoverySigned = user.discoverySigned ?: discoverySigned,
166+
discoveryURL = user.discoveryURL ?: discoveryURL,
167+
gossipEnabled = user.gossipEnabled ?: gossipEnabled,
168+
bootstrapPubKey = user.bootstrapPubKey ?: bootstrapPubKey,
169+
emergencyPolicyURL = user.emergencyPolicyURL ?: emergencyPolicyURL,
170+
emergencyPolicyPubKey = user.emergencyPolicyPubKey ?: emergencyPolicyPubKey,
171+
pathAggressive = user.pathAggressive ?: pathAggressive,
172+
pathCooldownMs = user.pathCooldownMs ?: pathCooldownMs,
173+
stunServers = user.stunServers ?: stunServers,
174+
turnUrls = user.turnUrls ?: turnUrls,
175+
gossipPeers = user.gossipPeers ?: gossipPeers,
176+
gossipIntervalSec = user.gossipIntervalSec ?: gossipIntervalSec,
177+
gossipMaxAgeSec = user.gossipMaxAgeSec ?: gossipMaxAgeSec,
178+
geoAllowCountries = user.geoAllowCountries ?: geoAllowCountries,
179+
geoDenyCountries = user.geoDenyCountries ?: geoDenyCountries,
180+
stakeMin = user.stakeMin ?: stakeMin,
181+
peerPathFromDiscovery = user.peerPathFromDiscovery ?: peerPathFromDiscovery,
182+
peerRelayUseQuic = user.peerRelayUseQuic ?: peerRelayUseQuic,
183+
peerRelayUseUdp = user.peerRelayUseUdp ?: peerRelayUseUdp,
184+
peerRelayUdpListen = user.peerRelayUdpListen ?: peerRelayUdpListen,
185+
peerRelayUdpAdvertise = user.peerRelayUdpAdvertise ?: peerRelayUdpAdvertise,
186+
peerQuicServerName = user.peerQuicServerName ?: peerQuicServerName,
187+
dhtFindUrls = user.dhtFindUrls ?: dhtFindUrls,
188+
stakeRegistryURL = user.stakeRegistryURL ?: stakeRegistryURL,
189+
stakeRegistryPubKey = user.stakeRegistryPubKey ?: stakeRegistryPubKey,
190+
stakeReputationFile = user.stakeReputationFile ?: stakeReputationFile,
191+
stakeBonusHttpUrl = user.stakeBonusHttpUrl ?: stakeBonusHttpUrl,
192+
stakeMerkleFile = user.stakeMerkleFile ?: stakeMerkleFile,
193+
stakeMerkleRootUrl = user.stakeMerkleRootUrl ?: stakeMerkleRootUrl,
194+
dhtRpcListenUdp = user.dhtRpcListenUdp ?: dhtRpcListenUdp,
195+
dhtRpcSecret = user.dhtRpcSecret ?: dhtRpcSecret,
196+
dhtRpcSeedPeers = user.dhtRpcSeedPeers ?: dhtRpcSeedPeers,
197+
dhtRpcIntervalSec = user.dhtRpcIntervalSec ?: dhtRpcIntervalSec,
198+
dhtRpcFindK = user.dhtRpcFindK ?: dhtRpcFindK,
199+
dhtIterativeRounds = user.dhtIterativeRounds ?: dhtIterativeRounds,
200+
dhtIterativeAlpha = user.dhtIterativeAlpha ?: dhtIterativeAlpha,
201+
dhtPublishSrflx = user.dhtPublishSrflx ?: dhtPublishSrflx,
202+
symmetricNatHolePunch = user.symmetricNatHolePunch ?: symmetricNatHolePunch,
203+
)
204+
}

0 commit comments

Comments
 (0)