Skip to content

Commit bab47e4

Browse files
committed
[bugfix] wait for validated network on reconnect
1 parent 95e728b commit bab47e4

6 files changed

Lines changed: 53 additions & 39 deletions

File tree

app/src/main/cpp/libs/fptn

Submodule fptn updated from ef5730a to dd2a120

app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class WrapperWebsocketClient final {
6565
const fptn::common::network::IPv6Address& ipv6);
6666

6767
private:
68-
const int kMaxReconnectionAttempts_ = 3;
68+
const int kMaxReconnectionAttempts_ = 1;
6969

7070
std::thread th_;
7171
mutable std::mutex mutex_;

app/src/main/java/org/fptn/vpn/services/vpn/FptnConnection.java

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@
3232
import android.content.pm.PackageManager;
3333
import android.net.ConnectivityManager;
3434
import android.net.IpPrefix;
35-
import android.net.Network;
36-
import android.net.NetworkCapabilities;
3735
import android.net.VpnService;
38-
import android.content.Context;
3936
import android.os.Build;
4037
import android.os.ParcelFileDescriptor;
4138

@@ -154,7 +151,8 @@ public FptnConnection(final FptnService service,
154151
final String preFetchedToken,
155152
final ConnectivityManager connectivityManager,
156153
final AdBlocker adBlocker,
157-
final String customDnsIpv4) {
154+
final String customDnsIpv4,
155+
final DnsServers preFetchedDnsServers) {
158156
this.service = service;
159157
this.connectionId = connectionId;
160158
this.serverEntity = serverEntity;
@@ -179,7 +177,8 @@ public FptnConnection(final FptnService service,
179177
sniHostName,
180178
censorshipStrategy,
181179
sniSpoofingMode,
182-
preFetchedToken
180+
preFetchedToken,
181+
preFetchedDnsServers
183182
);
184183
}
185184

@@ -371,6 +370,7 @@ private void configureConnectionTimeSpeedScheduler() {
371370

372371
private void configureAddressesAndRoutes(VpnService.Builder builder) throws UnknownHostException, PVNClientException {
373372
final DnsServers dnsServers = webSocketClient.getDnsServers();
373+
service.cacheDnsServers(serverEntity.getId(), dnsServers);
374374

375375
// Custom DNS (added first so it takes priority)
376376
if (customDnsIpv4 != null && !customDnsIpv4.isEmpty()) {
@@ -492,10 +492,8 @@ public void onConnectionFailure() {
492492
webSocketClient.isStarted(),
493493
tunValid,
494494
onFailureScheduledTask != null && !onFailureScheduledTask.isCancelled());
495-
boolean otherVpnActive = isOtherVpnActive();
496-
XLog.tag(TAG).w("[id=%d] VPN state check [tunValid=%b, otherVpnActive=%b]", connectionId, tunValid, otherVpnActive);
497-
if (!tunValid || otherVpnActive) {
498-
XLog.tag(TAG).i("[id=%d] Disconnecting silently [tunValid=%b, otherVpnActive=%b]", connectionId, tunValid, otherVpnActive);
495+
if (!tunValid) {
496+
XLog.tag(TAG).i("[id=%d] Disconnecting silently [tunValid=%b]", connectionId, tunValid);
499497
service.disconnectSilently(connectionId);
500498
return;
501499
}
@@ -590,30 +588,6 @@ private boolean isTunInterfaceValid(ParcelFileDescriptor vpnInterface) {
590588
return vpnInterface != null && vpnInterface.getFileDescriptor() != null && vpnInterface.getFileDescriptor().valid();
591589
}
592590

593-
private boolean isOtherVpnActive() {
594-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
595-
return false;
596-
}
597-
try {
598-
ConnectivityManager cm = (ConnectivityManager) service.getSystemService(Context.CONNECTIVITY_SERVICE);
599-
if (cm == null) return false;
600-
int myUid = service.getApplicationInfo().uid;
601-
for (Network network : cm.getAllNetworks()) {
602-
NetworkCapabilities caps = cm.getNetworkCapabilities(network);
603-
if (caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
604-
&& caps.getOwnerUid() != myUid) {
605-
return true;
606-
}
607-
}
608-
} catch (NoSuchMethodError e) {
609-
// Samsung Android 10 ships without getOwnerUid() despite API level 29
610-
XLog.tag(TAG).w("[id=%d] getOwnerUid() not available on this device", connectionId);
611-
} catch (Exception e) {
612-
XLog.tag(TAG).w("[id=%d] isOtherVpnActive check failed: %s", connectionId, e.getMessage());
613-
}
614-
return false;
615-
}
616-
617591
public void onNetworkChanged() {
618592
XLog.tag(TAG).i("[id=%d] Network changed — forcing clean reconnect [wsStarted=%b]",
619593
connectionId, webSocketClient.isStarted());

app/src/main/java/org/fptn/vpn/services/vpn/FptnService.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import org.fptn.vpn.enums.PerAppVpnMode;
6464
import org.fptn.vpn.enums.SniSpoofingMode;
6565
import org.fptn.vpn.services.tile.FptnTileService;
66+
import org.fptn.vpn.services.websocket.DnsServers;
6667
import org.fptn.vpn.utils.NetworkUtils;
6768
import org.fptn.vpn.utils.NotificationUtils;
6869
import org.fptn.vpn.utils.SharedPrefUtils;
@@ -110,6 +111,11 @@ public class FptnService extends VpnService {
110111
private ConnectivityManager.NetworkCallback networkWaitCallback;
111112
private volatile int pendingServerId = SELECTED_SERVER_ID_AUTO;
112113

114+
private volatile DnsServers cachedDnsServers = null;
115+
private volatile int cachedDnsServersServerId = -1;
116+
117+
private volatile boolean restoringSession = false;
118+
113119
// Pending Intent for launch MainActivity when notification tapped
114120
private PendingIntent launchMainActivityPendingIntent;
115121

@@ -202,6 +208,19 @@ public void sendExceptionToService(PVNClientException exception, int senderConne
202208
submittedConnectionAttempt = executorService.submit(this::handleFallbackToAllServers);
203209
return;
204210
}
211+
if (restoringSession && Objects.equals(exception.errorCode, ErrorCode.CONNECT_TO_SERVER_ERROR)) {
212+
if (!NetworkUtils.isOnline(connectivityManager)) {
213+
XLog.tag(TAG).i("Reconnect couldn't re-establish, offline — waiting for network [server=%d]",
214+
current.getServerEntity().getId());
215+
enterWaitingForNetwork(current.getServerEntity().getId());
216+
} else {
217+
XLog.tag(TAG).i("Reconnect couldn't re-establish — scanning for a working server");
218+
setActiveConnection(null);
219+
submittedConnectionAttempt = executorService.submit(this::handleFallbackToAllServers);
220+
}
221+
return;
222+
}
223+
restoringSession = false;
205224
disconnect(exception);
206225
if (Objects.equals(exception.errorCode, ErrorCode.RECONNECTING_FAILED)) {
207226
showReconnectionFailedNotification();
@@ -224,6 +243,11 @@ public void updateConnectionState(ConnectionState connectionState, int reconnect
224243
switchState(connectionState, reconnectionCount, disconnectReason);
225244
}
226245

246+
public void cacheDnsServers(int serverId, DnsServers dnsServers) {
247+
cachedDnsServers = dnsServers;
248+
cachedDnsServersServerId = serverId;
249+
}
250+
227251
/* Static methods to start/stop service */
228252
public synchronized static void startToConnect(Context context, ServerEntity serverEntity) {
229253
Intent intent = new Intent(context, FptnService.class);
@@ -325,6 +349,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
325349
XLog.tag(TAG).i("Received command [action=%s, state=%s]", intent.getAction(), currentState);
326350

327351
if (ACTION_CONNECT.equals(intent.getAction()) && !isActiveState) {
352+
restoringSession = false;
328353
if (submittedConnectionAttempt != null && !submittedConnectionAttempt.isDone()) {
329354
XLog.tag(TAG).w("Ignoring CONNECT — connection attempt already in progress [state=%s]", currentState);
330355
return START_STICKY;
@@ -463,6 +488,8 @@ private synchronized void unregisterNetworkCallback() {
463488

464489
public void enterWaitingForNetwork(int serverId) {
465490
XLog.tag(TAG).i("Network offline — entering WAITING_FOR_NETWORK [pendingServerId=%d]", serverId);
491+
restoringSession = true;
492+
466493
String serverInfo = getActionConnectServerInfo(); // capture before teardown nulls the connection
467494
pendingServerId = serverId;
468495
updateNotificationWithMessage(getString(R.string.waiting_for_network), serverInfo);
@@ -586,6 +613,7 @@ private void switchState(ConnectionState connectionState, int reconnectCount, St
586613
}
587614
case CONNECTING -> setConnectionState(ConnectionState.CONNECTING, null);
588615
case CONNECTED -> {
616+
restoringSession = false;
589617
String serverInfo = getActionConnectServerInfo();
590618
updateNotificationWithMessage(getString(R.string.connected_to) + serverInfo, "");
591619

@@ -711,6 +739,9 @@ private void connectInternal(ServerEntity serverEntity, String sniHostname, Stri
711739
boolean customDnsEnabled = SharedPrefUtils.getCustomDnsEnabled(this);
712740
String customDnsIpv4 = customDnsEnabled ? SharedPrefUtils.getCustomDnsIpv4(this) : null;
713741

742+
DnsServers preFetchedDnsServers = (cachedDnsServers != null && cachedDnsServersServerId == serverEntity.getId())
743+
? cachedDnsServers : null;
744+
714745
connection = new FptnConnection(
715746
this,
716747
nextConnectionId.getAndIncrement(),
@@ -729,7 +760,8 @@ private void connectInternal(ServerEntity serverEntity, String sniHostname, Stri
729760
preFetchedToken,
730761
connectivityManager,
731762
adBlocker,
732-
customDnsIpv4
763+
customDnsIpv4,
764+
preFetchedDnsServers
733765
);
734766
connection.setConfigureVpnIntent(launchMainActivityPendingIntent);
735767
connection.start();

app/src/main/java/org/fptn/vpn/services/websocket/WebSocketClientWrapper.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public class WebSocketClientWrapper {
5555

5656
private NativeWebSocketClientImpl nativeWebSocketClient;
5757
private String cachedAccessToken = null;
58+
private DnsServers cachedDnsServers = null;
5859

5960
@Getter
6061
private boolean shutdown = false;
@@ -68,7 +69,8 @@ public WebSocketClientWrapper(ServerEntity serverEntity,
6869
String sniHostName,
6970
BypassCensorshipMethod censorshipStrategy,
7071
SniSpoofingMode sniSpoofingMode,
71-
String preFetchedToken) {
72+
String preFetchedToken,
73+
DnsServers preFetchedDnsServers) {
7274
this.serverEntity = serverEntity;
7375
this.tunAddressIPv4 = tunAddressIPv4;
7476
this.tunAddressIPv6 = tunAddressIPv6;
@@ -82,6 +84,7 @@ public WebSocketClientWrapper(ServerEntity serverEntity,
8284
this.sniSpoofingMode = sniSpoofingMode;
8385

8486
this.cachedAccessToken = preFetchedToken;
87+
this.cachedDnsServers = preFetchedDnsServers;
8588

8689
this.nativeHttpsClient = new NativeHttpsClientImpl(
8790
serverEntity.getHost(),
@@ -209,6 +212,10 @@ private String getAccessToken() throws PVNClientException {
209212
}
210213

211214
public DnsServers getDnsServers() throws PVNClientException {
215+
if (cachedDnsServers != null) {
216+
XLog.d(getTag(), "Re-using cached DNS servers (skipping /api/v1/dns request)");
217+
return cachedDnsServers;
218+
}
212219
int maxAttempts = 7;
213220
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
214221
if (isShutdown() || Thread.currentThread().isInterrupted()) {
@@ -218,6 +225,7 @@ public DnsServers getDnsServers() throws PVNClientException {
218225
if (response != null && response.code == 200) {
219226
DnsServers dnsServers = new Gson().fromJson(response.body, DnsServers.class);
220227
XLog.tag(getTag()).i("DNS servers received: %s", dnsServers.toString());
228+
cachedDnsServers = dnsServers;
221229
return dnsServers;
222230
}
223231
XLog.tag(getTag()).w("DNS server request failed [attempt=%d/%d]", attempt, maxAttempts);

app/src/main/java/org/fptn/vpn/utils/NotificationUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static void configureNotificationChannels(Context context) {
4141
Constants.MAIN_NOTIFICATION_CHANNEL_VERSION_NUM,
4242
Constants.MAIN_NOTIFICATION_CHANNEL_GROUP_ID,
4343
context.getString(R.string.notification_group_name),
44-
NotificationManager.IMPORTANCE_LOW, true);
44+
NotificationManager.IMPORTANCE_DEFAULT, true);
4545

4646
// create error notification channel
4747
createOrUpdateNotificationChannel(context, notificationManager,
@@ -51,7 +51,7 @@ public static void configureNotificationChannels(Context context) {
5151
Constants.ERROR_NOTIFICATION_CHANNEL_VERSION_NUM,
5252
Constants.ERROR_NOTIFICATION_CHANNEL_GROUP_ID,
5353
context.getString(R.string.errors_notification_group_name),
54-
NotificationManager.IMPORTANCE_LOW, true);
54+
NotificationManager.IMPORTANCE_HIGH, true);
5555

5656
// add sni checker notification channel
5757
createOrUpdateNotificationChannel(context, notificationManager,

0 commit comments

Comments
 (0)