Skip to content

Commit f99e86c

Browse files
committed
Resolve "Respect serverlist-v2 fd=1 force-disconnect flag"
1 parent 3396f5b commit f99e86c

17 files changed

Lines changed: 1529 additions & 16 deletions

File tree

base/schemas/com.windscribe.vpn.localdatabase.WindscribeDatabase/43.json

Lines changed: 909 additions & 0 deletions
Large diffs are not rendered by default.

base/src/main/java/com/windscribe/vpn/api/response/ServerInventory.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,19 @@ data class ServerInventory(
2929
@SerializedName("amneziawg_config_id")
3030
@Expose
3131
var amneziaWgConfigId: String? = null,
32-
)
32+
) {
33+
/**
34+
* True when this response carries node changes that have not been applied locally yet. The API
35+
* keeps returning the same delta until the client reports the matching revision, so it is safe
36+
* to use this to kick off a server list update.
37+
*/
38+
fun hasPendingDelta(): Boolean = action == ACTION_DELTA && (enabled?.isNotEmpty() == true || disabled?.isNotEmpty() == true)
39+
40+
companion object {
41+
const val ACTION_DELTA = "delta"
42+
const val ACTION_HOLD = "hold"
43+
}
44+
}
3345

3446
@Keep
3547
data class DisabledServer(

base/src/main/java/com/windscribe/vpn/api/response/ServerResponse.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,7 @@ data class ServerData(
4747
@SerializedName("ipv6")
4848
@Expose
4949
val ipv6: Int = 0,
50+
@SerializedName("fd")
51+
@Expose
52+
val forceDisconnect: Int = 0,
5053
)

base/src/main/java/com/windscribe/vpn/backend/utils/WindVpnController.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import com.windscribe.vpn.repository.CallResult
4545
import com.windscribe.vpn.repository.EmergencyConnectRepository
4646
import com.windscribe.vpn.repository.LocationRepository
4747
import com.windscribe.vpn.repository.WgConfigRepository
48-
import com.windscribe.vpn.serverlist.entity.Datacenter
4948
import com.windscribe.vpn.serverlist.entity.Server
5049
import com.windscribe.vpn.services.canAccessNetworkName
5150
import com.windscribe.vpn.services.startAutoConnectService
@@ -153,9 +152,8 @@ open class WindVpnController
153152

154153
private var lastUsedRandomIndex = 0
155154

156-
private suspend fun getForcedNodeIndex(city: Datacenter): Int {
155+
private fun getForcedNodeIndex(nodes: List<Server>): Int {
157156
val forceNode = advanceParameterRepository.get().getForceNode()
158-
val nodes = localDbInterface.getServersByDatacenter(city.id)
159157
return if (forceNode != null && nodes.isNotEmpty()) {
160158
nodes.indexOfFirst { WindUtilities.hostnamesMatch(it.hostname, forceNode) }
161159
} else {
@@ -173,7 +171,10 @@ open class WindVpnController
173171
localDbInterface.getDatacenterAndLocation(selectedCity)
174172
?: throw Exception("City not found in database: $selectedCity")
175173
val city = cityAndRegion.datacenter
176-
val nodes = localDbInterface.getServersByDatacenter(city.id)
174+
val nodes = localDbInterface.getServersByDatacenter(city.id).filter { it.isConnectable }
175+
if (nodes.isEmpty()) {
176+
throw Exception("No eligible node in datacenter: ${city.id}")
177+
}
177178
val pinnedIp =
178179
localDbInterface
179180
.getFavouritesAsync()
@@ -189,7 +190,7 @@ open class WindVpnController
189190
}
190191
}
191192
// Node with forced hostname from advance parameter
192-
val forcedNodeIndex = getForcedNodeIndex(city)
193+
val forcedNodeIndex = getForcedNodeIndex(nodes)
193194
if (forcedNodeIndex != -1) {
194195
logger.debug("Forcing node to {}", nodes[forcedNodeIndex])
195196
randomIndex = forcedNodeIndex

base/src/main/java/com/windscribe/vpn/di/BaseApplicationModule.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ open class BaseApplicationModule {
183183
.addMigrations(Migrations.migration_39_40)
184184
.addMigrations(Migrations.migration_40_41)
185185
.addMigrations(Migrations.migration_41_42)
186+
.addMigrations(Migrations.migration_42_43)
186187
.build()
187188

188189
@Provides

base/src/main/java/com/windscribe/vpn/localdatabase/Migrations.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,15 @@ object Migrations {
260260
}
261261
}
262262

263+
val migration_42_43: Migration =
264+
object : Migration(42, 43) {
265+
override fun migrate(db: SupportSQLiteDatabase) {
266+
db.execSQL("ALTER TABLE Server ADD COLUMN force_disconnect INTEGER NOT NULL DEFAULT 0")
267+
invalidateData()
268+
logger.debug("Migrated db from version:42 to version:43 - Server force_disconnect column")
269+
}
270+
}
271+
263272
private fun invalidateData() {
264273
Windscribe.appContext.preference.migrationRequired = true
265274
}

base/src/main/java/com/windscribe/vpn/localdatabase/WindscribeDatabase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import javax.inject.Singleton
3939
Datacenter::class, Server::class, Favourite::class, PingTime::class, StaticRegion::class, NetworkInfo::class, ConfigFile::class,
4040
WindNotification::class, UnBlockWgParam::class, ExcludedIpDomain::class,
4141
],
42-
version = 42,
42+
version = 43,
4343
exportSchema = true,
4444
)
4545
@Singleton

base/src/main/java/com/windscribe/vpn/repository/ServerListRepository.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ class ServerListRepository
326326
weight = serverData.weight,
327327
health = serverData.health,
328328
ipv6 = serverData.ipv6,
329+
forceDisconnect = serverData.forceDisconnect,
329330
)
330331
}
331332
localDbInterface.deleteAllServers()
@@ -352,18 +353,22 @@ class ServerListRepository
352353
*/
353354
private suspend fun updateServersDelta(serverInventory: ServerInventory) {
354355
when (serverInventory.action) {
355-
"hold" -> {
356+
ServerInventory.ACTION_HOLD -> {
356357
logger.debug("V2: Hold action received - not updating this session")
357358
return
358359
}
359360

360-
"delta" -> {
361+
ServerInventory.ACTION_DELTA -> {
361362
val enabledCount = serverInventory.enabled?.size ?: 0
362363
val disabledCount = serverInventory.disabled?.size ?: 0
363364
if (enabledCount == 0 && disabledCount == 0) {
364365
return
365366
}
366-
logger.debug("V2: Delta update - enabling $enabledCount servers, disabling $disabledCount servers")
367+
val forceDisconnectCount = serverInventory.enabled?.count { it.forceDisconnect == 1 } ?: 0
368+
logger.debug(
369+
"V2: Delta update - enabling $enabledCount servers, disabling $disabledCount servers, " +
370+
"$forceDisconnectCount flagged for force disconnect",
371+
)
367372
serverInventory.enabled?.let { enabledServers ->
368373
val servers =
369374
enabledServers.map { serverData ->
@@ -376,6 +381,8 @@ class ServerListRepository
376381
datacenterId = serverData.datacenterId,
377382
weight = serverData.weight,
378383
health = serverData.health,
384+
ipv6 = serverData.ipv6,
385+
forceDisconnect = serverData.forceDisconnect,
379386
)
380387
}
381388
localDbInterface.addServers(servers)

base/src/main/java/com/windscribe/vpn/serverlist/dao/ServerDao.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@ abstract class ServerDao {
2929
@Query("DELETE FROM Server")
3030
abstract suspend fun deleteAll()
3131

32-
@Query("SELECT * FROM Server WHERE datacenter_id = :datacenterId ORDER BY weight DESC")
32+
@Query("SELECT * FROM Server WHERE datacenter_id = :datacenterId AND force_disconnect = 0 ORDER BY weight DESC")
3333
abstract suspend fun getServersByDatacenter(datacenterId: Int): List<Server>
3434

3535
@Query("SELECT * FROM Server WHERE server_id = :serverId")
3636
abstract suspend fun getServerById(serverId: Int): Server?
3737

38-
@Query("SELECT * FROM Server ORDER BY datacenter_id, weight DESC")
38+
@Query("SELECT * FROM Server WHERE force_disconnect = 0 ORDER BY datacenter_id, weight DESC")
3939
abstract suspend fun getAllServers(): List<Server>
4040

41-
@Query("SELECT * FROM Server ORDER BY datacenter_id, weight DESC")
41+
@Query("SELECT * FROM Server WHERE force_disconnect = 0 ORDER BY datacenter_id, weight DESC")
4242
abstract fun observeAllServers(): Flow<List<Server>>
4343

4444
@Query("SELECT COUNT(*) FROM Server")

base/src/main/java/com/windscribe/vpn/serverlist/entity/Server.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,19 @@ data class Server(
5555
@Expose
5656
@ColumnInfo(name = "ipv6", defaultValue = "0")
5757
val ipv6: Int = 0,
58-
)
58+
/**
59+
* Node level force disconnect flag. Set to 1 while the node is being drained for maintenance.
60+
* Drained nodes stay in the server list so connected clients can move away gracefully, but
61+
* they must not be used for new connections. See [isConnectable].
62+
*/
63+
@SerializedName("fd")
64+
@Expose
65+
@ColumnInfo(name = "force_disconnect", defaultValue = "0")
66+
val forceDisconnect: Int = 0,
67+
) {
68+
/**
69+
* A node is a valid connection candidate unless the server list flagged it for force disconnect.
70+
*/
71+
val isConnectable: Boolean
72+
get() = forceDisconnect != 1
73+
}

0 commit comments

Comments
 (0)