From e0350fbee3a54182aefecf2b985ac1340c993753 Mon Sep 17 00:00:00 2001 From: brightsunshine54 Date: Tue, 21 Apr 2026 23:51:03 +0700 Subject: [PATCH 1/2] add IPv6 support --- app/src/main/cpp/src/websocket_client.cpp | 18 +-- .../wrapper_websocket_client.cpp | 5 +- .../wrapper_websocket_client.h | 2 + .../org/fptn/vpn/enums/ConnectionSubnets.java | 52 +++++--- .../snichecker/SniCheckerService.java | 2 +- .../fptn/vpn/services/vpn/FptnConnection.java | 120 ++++++++++++------ .../fptn/vpn/services/vpn/FptnService.java | 10 +- .../vpn/services/websocket/DnsServers.java | 12 ++ .../websocket/NativeWebSocketClientImpl.java | 7 +- .../websocket/WebSocketClientWrapper.java | 31 ++--- .../main/java/org/fptn/vpn/utils/IPUtils.java | 22 ++-- .../java/org/fptn/vpn/utils/NetworkUtils.java | 4 +- .../org/fptn/vpn/utils/NotificationUtils.java | 2 +- gradle/libs.versions.toml | 6 +- 14 files changed, 191 insertions(+), 102 deletions(-) create mode 100644 app/src/main/java/org/fptn/vpn/services/websocket/DnsServers.java diff --git a/app/src/main/cpp/src/websocket_client.cpp b/app/src/main/cpp/src/websocket_client.cpp index 4034f8bd..55abc625 100644 --- a/app/src/main/cpp/src/websocket_client.cpp +++ b/app/src/main/cpp/src/websocket_client.cpp @@ -67,6 +67,7 @@ Java_org_fptn_vpn_services_websocket_NativeWebSocketClientImpl_nativeCreate( jstring server_ip_param, jint server_port_param, jstring tun_ipv4_param, + jstring tun_ipv6_param, jstring sni_param, jstring access_token_param, jstring expected_md5_fingerprint_param, @@ -76,24 +77,25 @@ Java_org_fptn_vpn_services_websocket_NativeWebSocketClientImpl_nativeCreate( auto server_ip = fptn::wrapper::ConvertToCString(env, server_ip_param); int server_port = server_port_param; auto tun_ipv4 = fptn::wrapper::ConvertToCString(env, tun_ipv4_param); + auto tun_ipv6 = fptn::wrapper::ConvertToCString(env, tun_ipv6_param); auto sni = fptn::wrapper::ConvertToCString(env, sni_param); auto access_token = fptn::wrapper::ConvertToCString(env, access_token_param); auto expected_md5_fingerprint = fptn::wrapper::ConvertToCString(env, expected_md5_fingerprint_param); - const auto censorship_strategy_name = fptn::wrapper::ConvertToCString( + const auto censorship_strategy_name = fptn::wrapper::ConvertToCString( env,censorship_strategy_name_param); - fptn::protocol::https::CensorshipStrategy censorship_strategy = + fptn::protocol::https::CensorshipStrategy censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSni; - if (censorship_strategy_name == "OBFUSCATION") { - censorship_strategy = fptn::protocol::https::CensorshipStrategy::kTlsObfuscator; - } else if (censorship_strategy_name == "SNI-REALITY") { - censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityMode; - } + if (censorship_strategy_name == "OBFUSCATION") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kTlsObfuscator; + } else if (censorship_strategy_name == "SNI-REALITY") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityMode; + } jobject global_object_ref = env->NewWeakGlobalRef(thiz); auto* websocket_client = new WrapperWebsocketClient(global_object_ref, - std::move(server_ip), server_port, std::move(tun_ipv4), std::move(sni), + std::move(server_ip), server_port, std::move(tun_ipv4), std::move(tun_ipv6), std::move(sni), std::move(access_token), std::move(expected_md5_fingerprint), censorship_strategy); auto jobj_client = reinterpret_cast(websocket_client); diff --git a/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.cpp b/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.cpp index 9439fc87..7e4f7414 100644 --- a/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.cpp +++ b/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.cpp @@ -23,6 +23,7 @@ WrapperWebsocketClient::WrapperWebsocketClient(jobject wrapper, std::string server_ip, int server_port, std::string tun_ipv4, + std::string tun_ipv6, std::string sni, std::string access_token, std::string expected_md5_fingerprint, @@ -33,6 +34,7 @@ WrapperWebsocketClient::WrapperWebsocketClient(jobject wrapper, server_ip_(std::move(server_ip)), server_port_(server_port), tun_ipv4_(std::move(tun_ipv4)), + tun_ipv6_(std::move(tun_ipv6)), sni_(std::move(sni)), access_token_(std::move(access_token)), expected_md5_fingerprint_(std::move(expected_md5_fingerprint)), @@ -97,8 +99,7 @@ void WrapperWebsocketClient::Run() { try { const auto server_ip_addr = fptn::common::network::IPv4Address::Create(server_ip_); const auto tun_ipv4_addr = fptn::common::network::IPv4Address::Create(tun_ipv4_); - const auto tun_ipv6_addr = fptn::common::network::IPv6Address::Create( - FPTN_CLIENT_DEFAULT_ADDRESS_IP6); + const auto tun_ipv6_addr = fptn::common::network::IPv6Address::Create(tun_ipv6_); if (!server_ip_addr.IsValid() || !tun_ipv4_addr.IsValid() || !tun_ipv6_addr.IsValid()) { SPDLOG_ERROR( diff --git a/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.h b/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.h index e9add55c..d912025a 100644 --- a/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.h +++ b/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.h @@ -20,6 +20,7 @@ class WrapperWebsocketClient final { std::string server_ip, int server_port, std::string tun_ipv4, + std::string tun_ipv6, std::string sni, std::string access_token, std::string expected_md5_fingerprint, @@ -55,6 +56,7 @@ class WrapperWebsocketClient final { const std::string server_ip_; const int server_port_; const std::string tun_ipv4_; + const std::string tun_ipv6_; const std::string sni_; const std::string access_token_; const std::string expected_md5_fingerprint_; diff --git a/app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java b/app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java index 02c07eda..8e033763 100644 --- a/app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java +++ b/app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java @@ -5,36 +5,52 @@ import androidx.annotation.RequiresApi; -import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.UnknownHostException; import lombok.Getter; @Getter public enum ConnectionSubnets { - TUN_ADDRESS("10.10.0.1", 32), - TUN_INTERFACE_SUBNET("10.10.0.0", 16), - FPTN_SUBNET("172.16.0.0", 12), - LOCAL_SUBNET("192.168.0.0", 16), - ALL_SUBNET("0.0.0.0", 0), - - // todo: rename me! - HZ_WHAT_IS_THIS_IP("172.20.0.1", 32); + LOCAL_TUN_ADDRESS("10.10.0.1", 32, "fd00::1", 128), + LOCAL_TUN_INTERFACE_SUBNET("10.10.0.0", 16, "fd00::", 64), + FPTN_SERVER_SUBNET("172.16.0.0", 12, "fc00:1::", 64), + LOCAL_SUBNET("192.168.0.0", 16, "::", 128), + ALL_SUBNET("0.0.0.0", 0, "::", 128); + + public static final int IP_V4_PREFIX_LENGTH = 32; + public static final int IP_V6_PREFIX_LENGTH = 128; + + private final String ipV4Address; + private final int v4prefix; + + private final String ipV6Address; + private final int v6prefix; + + ConnectionSubnets(String ipV4Address, int v4prefix, String ipV6Address, int v6prefix) { + this.ipV4Address = ipV4Address; + this.v4prefix = v4prefix; + this.ipV6Address = ipV6Address; + this.v6prefix = v6prefix; + } - private final String ipAddress; - private final int prefix; + @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) + public IpPrefix getAsIpV4Prefix() throws UnknownHostException { + return new IpPrefix(Inet4Address.getByName(ipV4Address), v4prefix); + } - ConnectionSubnets(String ipAddress, int prefix) { - this.ipAddress = ipAddress; - this.prefix = prefix; + public String getAsIpV4PrefixAsString() { + return ipV4Address + "/" + v4prefix; } @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) - public IpPrefix getAsIpPrefix() throws UnknownHostException { - return new IpPrefix(InetAddress.getByName(ipAddress), prefix); + public IpPrefix getAsIpV6Prefix() throws UnknownHostException { + return new IpPrefix(Inet6Address.getByName(ipV6Address), v6prefix); } - public String getAsIPWithPrefix() { - return ipAddress + "/" + prefix; + public String getAsIpV6PrefixAsString() { + return ipV6Address + "/" + v6prefix; } + } diff --git a/app/src/main/java/org/fptn/vpn/services/snichecker/SniCheckerService.java b/app/src/main/java/org/fptn/vpn/services/snichecker/SniCheckerService.java index 7ad04f08..12c4ceb8 100644 --- a/app/src/main/java/org/fptn/vpn/services/snichecker/SniCheckerService.java +++ b/app/src/main/java/org/fptn/vpn/services/snichecker/SniCheckerService.java @@ -113,7 +113,7 @@ public void onCreate() { super.onCreate(); // Configure notification channels - NotificationUtils.configureNotificationChannel(this); + NotificationUtils.configureNotificationChannels(this); // Pending Intent for launch byPassMethodActivity when notification tapped launchActivityPendingIntent = PendingIntent.getActivity(this, 0, diff --git a/app/src/main/java/org/fptn/vpn/services/vpn/FptnConnection.java b/app/src/main/java/org/fptn/vpn/services/vpn/FptnConnection.java index fabba69f..c5e92a7c 100644 --- a/app/src/main/java/org/fptn/vpn/services/vpn/FptnConnection.java +++ b/app/src/main/java/org/fptn/vpn/services/vpn/FptnConnection.java @@ -1,11 +1,12 @@ package org.fptn.vpn.services.vpn; import static org.fptn.vpn.enums.ConnectionSubnets.ALL_SUBNET; -import static org.fptn.vpn.enums.ConnectionSubnets.FPTN_SUBNET; -import static org.fptn.vpn.enums.ConnectionSubnets.HZ_WHAT_IS_THIS_IP; +import static org.fptn.vpn.enums.ConnectionSubnets.FPTN_SERVER_SUBNET; +import static org.fptn.vpn.enums.ConnectionSubnets.IP_V4_PREFIX_LENGTH; +import static org.fptn.vpn.enums.ConnectionSubnets.IP_V6_PREFIX_LENGTH; import static org.fptn.vpn.enums.ConnectionSubnets.LOCAL_SUBNET; -import static org.fptn.vpn.enums.ConnectionSubnets.TUN_ADDRESS; -import static org.fptn.vpn.enums.ConnectionSubnets.TUN_INTERFACE_SUBNET; +import static org.fptn.vpn.enums.ConnectionSubnets.LOCAL_TUN_ADDRESS; +import static org.fptn.vpn.enums.ConnectionSubnets.LOCAL_TUN_INTERFACE_SUBNET; import android.app.PendingIntent; import android.content.pm.PackageManager; @@ -20,6 +21,7 @@ import org.fptn.vpn.enums.ConnectionState; import org.fptn.vpn.enums.NetworkType; import org.fptn.vpn.enums.PerAppVpnMode; +import org.fptn.vpn.services.websocket.DnsServers; import org.fptn.vpn.services.websocket.WebSocketAlreadyShutdownException; import org.fptn.vpn.services.websocket.WebSocketClientWrapper; import org.fptn.vpn.utils.DataRateCalculator; @@ -32,6 +34,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.InetAddress; +import java.net.UnknownHostException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -112,7 +115,7 @@ public FptnConnection(final FptnService service, final String sniHostName, final BypassCensorshipMethod censorshipStrategy, final PerAppVpnMode perAppVpnMode, - final List appInfos) { + final List appInfos) throws UnknownHostException { this.service = service; this.connectionId = connectionId; this.serverEntity = serverEntity; @@ -122,15 +125,19 @@ public FptnConnection(final FptnService service, this.censorshipStrategy = censorshipStrategy; this.perAppVpnMode = perAppVpnMode; this.appInfos = appInfos; + + InetAddress inetAddress = InetAddress.getByName(serverEntity.getHost()); this.webSocketClient = new WebSocketClientWrapper( this.serverEntity, - TUN_ADDRESS.getIpAddress(), + LOCAL_TUN_ADDRESS.getIpV4Address(), + LOCAL_TUN_ADDRESS.getIpV6Address(), this::onConnectionOpen, this::onMessageReceived, this::onConnectionFailure, this.sniHostName, this.censorshipStrategy ); + this.maxReconnectCount = maxReconnectCount; this.delayBetweenAttempts = delayBetweenAttempts; } @@ -143,16 +150,13 @@ public void run() { sendConnectionStateToService(ConnectionState.CONNECTING); VpnService.Builder builder = service.new Builder(); - builder.addAddress(TUN_ADDRESS.getIpAddress(), TUN_ADDRESS.getPrefix()); - builder.addRoute(HZ_WHAT_IS_THIS_IP.getIpAddress(), HZ_WHAT_IS_THIS_IP.getPrefix()); - builder.setMtu(MAX_PACKET_SIZE); - // enable blocking reading - builder.setBlocking(true); - - /* - From documentation: You can create either an allowed list, or, a disallowed list, but not both - */ - if (perAppVpnMode == PerAppVpnMode.ONLY_ALLOWED){ + builder.setBlocking(true) // enable blocking reading EXTREMELY IMPORTANT + .setSession(serverEntity.getName()) + .setConfigureIntent(configureVpnIntent) + .setMtu(MAX_PACKET_SIZE); + + // From documentation: You can create either an allowed list, or, a disallowed list, but not both + if (perAppVpnMode == PerAppVpnMode.ONLY_ALLOWED) { for (AppInfo appInfo : appInfos) { String packageName = appInfo.getPackageName(); try { @@ -161,7 +165,7 @@ public void run() { Log.d(TAG, "Package not found: " + packageName); } } - } else if (perAppVpnMode == PerAppVpnMode.EXCEPT_DISALLOWED){ + } else if (perAppVpnMode == PerAppVpnMode.EXCEPT_DISALLOWED) { for (AppInfo appInfo : appInfos) { String packageName = appInfo.getPackageName(); try { @@ -171,37 +175,79 @@ public void run() { } } } + InetAddress inetAddress = InetAddress.getByName(serverEntity.getHost()); + + DnsServers dns_server = webSocketClient.getDnsServers(); + + // IPv4 + builder.addDnsServer(dns_server.getIpv4()); + builder.addAddress(LOCAL_TUN_ADDRESS.getIpV4Address(), LOCAL_TUN_ADDRESS.getV4prefix()); + builder.addRoute(dns_server.getIpv4(), IP_V4_PREFIX_LENGTH); + + // IPv6 + builder.addDnsServer(dns_server.getIpv6()); + builder.addAddress(LOCAL_TUN_ADDRESS.getIpV6Address(), LOCAL_TUN_ADDRESS.getV6prefix()); + builder.addRoute(dns_server.getIpv6(), IP_V6_PREFIX_LENGTH); - final String dnsServer = webSocketClient.getDnsServerIPv4(); - builder.addDnsServer(dnsServer); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - builder.excludeRoute(new IpPrefix(InetAddress.getByName(serverEntity.getHost()), 32)); - builder.excludeRoute(TUN_INTERFACE_SUBNET.getAsIpPrefix()); - builder.excludeRoute(FPTN_SUBNET.getAsIpPrefix()); - builder.excludeRoute(LOCAL_SUBNET.getAsIpPrefix()); - builder.addRoute(ALL_SUBNET.getIpAddress(), ALL_SUBNET.getPrefix()); + //if (inetAddress instanceof Inet4Address) { + builder.excludeRoute(new IpPrefix(inetAddress, IP_V4_PREFIX_LENGTH)); + //} else if (inetAddress instanceof Inet6Address){ + // builder.excludeRoute(new IpPrefix(inetAddress, IP_V6_PREFIX_LENGTH)); //todo: add Server IPv6 + //} + + builder.excludeRoute(LOCAL_TUN_INTERFACE_SUBNET.getAsIpV4Prefix()); + builder.excludeRoute(LOCAL_TUN_INTERFACE_SUBNET.getAsIpV6Prefix()); + + builder.excludeRoute(FPTN_SERVER_SUBNET.getAsIpV4Prefix()); + builder.excludeRoute(FPTN_SERVER_SUBNET.getAsIpV6Prefix()); + + builder.excludeRoute(LOCAL_SUBNET.getAsIpV4Prefix()); + builder.excludeRoute(LOCAL_SUBNET.getAsIpV6Prefix()); + + builder.addRoute(ALL_SUBNET.getIpV4Address(), ALL_SUBNET.getV4prefix()); + builder.addRoute(ALL_SUBNET.getIpV6Address(), ALL_SUBNET.getV6prefix()); } else { - IPAddress rootSubnet = new IPAddressString(ALL_SUBNET.getAsIPWithPrefix()).getAddress(); - List subnetsToExclude = Stream.of( - serverEntity.getHost() + "/32", - TUN_INTERFACE_SUBNET.getAsIPWithPrefix(), - FPTN_SUBNET.getAsIPWithPrefix(), - LOCAL_SUBNET.getAsIPWithPrefix() + // for IPv4 + IPAddress rootSubnetV4 = new IPAddressString(ALL_SUBNET.getAsIpV4PrefixAsString()).getAddress(); + List subnetsToExcludeV4 = Stream.of( + String.format("%s/%s", serverEntity.getHost(), IP_V4_PREFIX_LENGTH), + LOCAL_TUN_INTERFACE_SUBNET.getAsIpV4PrefixAsString(), + FPTN_SERVER_SUBNET.getAsIpV4PrefixAsString(), + LOCAL_SUBNET.getAsIpV4PrefixAsString() ) .map(sub -> new IPAddressString(sub).getAddress()) .collect(Collectors.toList()); - List subnetsToInclude = new ArrayList<>(); - IPUtils.exclude(rootSubnet, subnetsToExclude, subnetsToInclude); - for (IPAddress ipAddress : subnetsToInclude) { + List subnetsToIncludeV4 = new ArrayList<>(); + IPUtils.exclude(rootSubnetV4, subnetsToExcludeV4, subnetsToIncludeV4, IP_V4_PREFIX_LENGTH); + for (IPAddress ipAddress : subnetsToIncludeV4) { String hostIp = ipAddress.getLower().toAddressString().getHostAddress().toString(); Integer networkPrefixLength = ipAddress.getLower().toAddressString().getNetworkPrefixLength(); - Log.d(getTag(), "subnetsToInclude.ipAddress: " + hostIp + "/" + networkPrefixLength); - builder.addRoute(hostIp, networkPrefixLength != null ? networkPrefixLength : 32); + Log.d(getTag(), "subnetsToIncludeV4.ipAddress: " + hostIp + "/" + networkPrefixLength); + builder.addRoute(hostIp, networkPrefixLength != null ? networkPrefixLength : IP_V4_PREFIX_LENGTH); } - } - builder.setSession(serverEntity.getName()).setConfigureIntent(configureVpnIntent); + // for IPv6 + IPAddress rootSubnetV6 = new IPAddressString(ALL_SUBNET.getAsIpV4PrefixAsString()).getAddress(); + List subnetsToExcludeV6 = Stream.of( + // String.format("%s/%s", serverEntity.getHost(), IP_V6_PREFIX_LENGTH), //todo: add Server IPv6 + LOCAL_TUN_INTERFACE_SUBNET.getAsIpV6PrefixAsString(), + FPTN_SERVER_SUBNET.getAsIpV6PrefixAsString(), + LOCAL_SUBNET.getAsIpV6PrefixAsString() + ) + .map(sub -> new IPAddressString(sub).getAddress()) + .collect(Collectors.toList()); + + List subnetsToIncludeV6 = new ArrayList<>(); + IPUtils.exclude(rootSubnetV6, subnetsToExcludeV6, subnetsToIncludeV6, IP_V6_PREFIX_LENGTH); + for (IPAddress ipAddress : subnetsToIncludeV6) { + String hostIp = ipAddress.getLower().toAddressString().getHostAddress().toString(); + Integer networkPrefixLength = ipAddress.getLower().toAddressString().getNetworkPrefixLength(); + Log.d(getTag(), "subnetsToIncludeV6.ipAddress: " + hostIp + "/" + networkPrefixLength); + builder.addRoute(hostIp, networkPrefixLength != null ? networkPrefixLength : IP_V6_PREFIX_LENGTH); + } + } synchronized (service) { vpnInterface = builder.establish(); diff --git a/app/src/main/java/org/fptn/vpn/services/vpn/FptnService.java b/app/src/main/java/org/fptn/vpn/services/vpn/FptnService.java index e0513854..99bdf654 100644 --- a/app/src/main/java/org/fptn/vpn/services/vpn/FptnService.java +++ b/app/src/main/java/org/fptn/vpn/services/vpn/FptnService.java @@ -39,6 +39,7 @@ import org.fptn.vpn.enums.PerAppVpnMode; import org.fptn.vpn.services.tile.FptnTileService; import org.fptn.vpn.utils.NetworkUtils; +import org.fptn.vpn.utils.NotificationUtils; import org.fptn.vpn.utils.SharedPrefUtils; import org.fptn.vpn.views.perappvpn.AppInfo; import org.fptn.vpn.services.speedtest.SpeedTestUtils; @@ -46,6 +47,7 @@ import org.fptn.vpn.vpnclient.exception.ErrorCode; import org.fptn.vpn.vpnclient.exception.PVNClientException; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -174,6 +176,9 @@ public synchronized static void startToDisconnect(Context context) { public void onCreate() { Log.i(TAG, "FptnService.onCreate() Thread.Id: " + Thread.currentThread().getId()); + // Configure notification channels + NotificationUtils.configureNotificationChannels(this); + // Get database instance (this need context! context may not exist earlier!) appDatabase = AppDatabase.getInstance(this); @@ -284,7 +289,8 @@ public int onStartCommand(Intent intent, int flags, int startId) { connect(server, sniHostname); } - } catch (ExecutionException | InterruptedException | RuntimeException e) { + } catch (ExecutionException | InterruptedException | RuntimeException | + UnknownHostException e) { disconnect(new PVNClientException(e.getMessage())); } }); @@ -414,7 +420,7 @@ private void setConnectionState(ConnectionState connectionState, PVNClientExcept .build()); } - private void connect(ServerEntity serverEntity, String sniHostname) { + private void connect(ServerEntity serverEntity, String sniHostname) throws UnknownHostException { // Moving VPNService to foreground to give it higher priority in system updateNotificationWithMessage(getString(R.string.connecting_to) + serverEntity.getServerInfo(), ""); diff --git a/app/src/main/java/org/fptn/vpn/services/websocket/DnsServers.java b/app/src/main/java/org/fptn/vpn/services/websocket/DnsServers.java new file mode 100644 index 00000000..c94e5401 --- /dev/null +++ b/app/src/main/java/org/fptn/vpn/services/websocket/DnsServers.java @@ -0,0 +1,12 @@ +package org.fptn.vpn.services.websocket; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +public class DnsServers { + @SerializedName("dns") + private String ipv4; + @SerializedName("dns_ipv6") + private String ipv6; +} diff --git a/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java b/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java index 44fbdb39..63af9e0c 100644 --- a/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java +++ b/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java @@ -31,7 +31,8 @@ public class NativeWebSocketClientImpl { public NativeWebSocketClientImpl( String host, int port, - String tunAddress, + String tunAddressIPv4, + String tunAddressIPv6, String accessToken, String md5ServerFingerprint, OnOpenCallback onOpenCallback, @@ -53,7 +54,8 @@ public NativeWebSocketClientImpl( this.nativeHandle = nativeCreate( host, port, - tunAddress, + tunAddressIPv4, + tunAddressIPv6, sniHostName, accessToken, md5ServerFingerprint, @@ -134,6 +136,7 @@ public void onMessageImpl(byte[] msg) { private native long nativeCreate(String server_ip, int server_port, String tun_ipv4, + String tun_ipv6, String sni, String access_token, String expected_md5_fingerprint, diff --git a/app/src/main/java/org/fptn/vpn/services/websocket/WebSocketClientWrapper.java b/app/src/main/java/org/fptn/vpn/services/websocket/WebSocketClientWrapper.java index 18aa0c8f..3bed5863 100644 --- a/app/src/main/java/org/fptn/vpn/services/websocket/WebSocketClientWrapper.java +++ b/app/src/main/java/org/fptn/vpn/services/websocket/WebSocketClientWrapper.java @@ -21,7 +21,8 @@ public class WebSocketClientWrapper { private static final String LOGIN_URL = "/api/v1/login"; private final ServerEntity serverEntity; - private final String tunAddress; + private final String tunAddressIPv4; + private final String tunAddressIPv6; private final OnOpenCallback onOpenCallback; private final OnMessageReceivedCallback onMessageReceivedCallback; private final OnFailureCallback onFailureCallback; @@ -35,14 +36,16 @@ public class WebSocketClientWrapper { private boolean shutdown = false; public WebSocketClientWrapper(ServerEntity serverEntity, - String tunAddress, + String tunAddressIPv4, + String tunAddressIPv6, OnOpenCallback onOpenCallback, OnMessageReceivedCallback onMessageReceivedCallback, OnFailureCallback onFailureCallback, String sniHostName, BypassCensorshipMethod censorshipStrategy) { this.serverEntity = serverEntity; - this.tunAddress = tunAddress; + this.tunAddressIPv4 = tunAddressIPv4; + this.tunAddressIPv6 = tunAddressIPv6; this.onOpenCallback = onOpenCallback; this.onMessageReceivedCallback = onMessageReceivedCallback; this.onFailureCallback = onFailureCallback; @@ -71,7 +74,8 @@ public synchronized void startWebSocket() throws PVNClientException, WebSocketAl nativeWebSocketClient = new NativeWebSocketClientImpl( serverEntity.getHost(), serverEntity.getPort(), - tunAddress, + tunAddressIPv4, + tunAddressIPv6, accessToken, serverEntity.getMd5ServerFingerprint(), onOpenCallback, @@ -142,24 +146,17 @@ private String getAccessToken() throws PVNClientException { throw new PVNClientException(ErrorCode.CONNECT_TO_SERVER_ERROR); } - public String getDnsServerIPv4() throws PVNClientException { + public DnsServers getDnsServers() throws PVNClientException { NativeResponse response = nativeHttpsClient.Get(DNS_URL, 15); - if (response != null) { - if (response.code == 200) { - try { - JSONObject jsonResponse = new JSONObject(response.body); - String dnsServer = jsonResponse.getString("dns"); - Log.i(getTag(), "DNS " + dnsServer + " retrieval successful."); - return dnsServer; - } catch (JSONException e) { - Log.e(getTag(), "Some error occurs on receiving DNS response: " + e); - throw new PVNClientException(ErrorCode.CONNECT_TO_SERVER_ERROR); - } - } + if (response != null && response.code == 200) { + DnsServers dnsServers = new Gson().fromJson(response.body, DnsServers.class); + Log.i(getTag(), "DnsServers: " + dnsServers.toString()); + return dnsServers; } throw new PVNClientException(ErrorCode.CONNECT_TO_SERVER_ERROR); } + private String getTag() { return this.getClass().getCanonicalName(); } diff --git a/app/src/main/java/org/fptn/vpn/utils/IPUtils.java b/app/src/main/java/org/fptn/vpn/utils/IPUtils.java index dff81a99..b9cbbb8f 100644 --- a/app/src/main/java/org/fptn/vpn/utils/IPUtils.java +++ b/app/src/main/java/org/fptn/vpn/utils/IPUtils.java @@ -1,5 +1,7 @@ package org.fptn.vpn.utils; +import android.util.Log; + import java.util.List; import java.util.Optional; @@ -7,26 +9,26 @@ import inet.ipaddr.IPAddressString; public class IPUtils { - public static void exclude(IPAddress rootSubnet, List subnetsToExclude, List afterExclude) { + public static void exclude(IPAddress rootSubnet, List subnetsToExclude, List afterExclude, int prefix) { Optional any = subnetsToExclude.stream().filter(subnet -> subnet.equals(rootSubnet)).findAny(); if (any.isPresent()) { // we reach minimum size target subnet - //System.out.println("rootSubnet: " + rootSubnet + " == any: " + any.get()); + //Log.d(IPUtils.class.getSimpleName(), "rootSubnet: " + rootSubnet + " == any: " + any.get()); return; } int newNetmaskBits = rootSubnet.getNetworkPrefixLength() + 1; - if (newNetmaskBits > 32) { + if (newNetmaskBits > prefix) { //System.out.println("EXCEED NETMASK BITS COUNT"); return; } IPAddress rootSubnetLower = rootSubnet.getLower(); IPAddress subnetLeft = new IPAddressString(rootSubnetLower.toAddressString().getHostAddress() + "/" + newNetmaskBits).getAddress(); - //System.out.println("SubnetLeft: " + subnetLeft + " start from: " + subnetLeft.getLower() + " to: " + subnetLeft.getUpper()); + //Log.d(IPUtils.class.getSimpleName(), "SubnetLeft: " + subnetLeft + " start from: " + subnetLeft.getLower() + " to: " + subnetLeft.getUpper()); Optional checkLeft = subnetsToExclude.stream().filter(subnetLeft::contains).findFirst(); if (checkLeft.isPresent()) { - exclude(subnetLeft, subnetsToExclude, afterExclude); + exclude(subnetLeft, subnetsToExclude, afterExclude, prefix); } else { afterExclude.add(subnetLeft); } @@ -35,12 +37,12 @@ public static void exclude(IPAddress rootSubnet, List subnetsToExclud //System.out.println("subtract: " + subtract); if (subtract != null && subtract.length > 0) { IPAddress subnetRight = subtract[0]; - //System.out.println("SubnetRight: " + subnetRight + " start from: " + subnetRight.getLower() + " to: " + subnetRight.getUpper()); - Optional checkRight = subnetsToExclude.stream().filter(subnetRight::contains - - ).findFirst(); + //Log.d(IPUtils.class.getSimpleName(), "SubnetRight: " + subnetRight + " start from: " + subnetRight.getLower() + " to: " + subnetRight.getUpper()); + Optional checkRight = subnetsToExclude.stream() + .filter(subnetRight::contains) + .findFirst(); if (checkRight.isPresent()) { - exclude(subnetRight, subnetsToExclude, afterExclude); + exclude(subnetRight, subnetsToExclude, afterExclude, prefix); } else { afterExclude.add(subnetRight); } diff --git a/app/src/main/java/org/fptn/vpn/utils/NetworkUtils.java b/app/src/main/java/org/fptn/vpn/utils/NetworkUtils.java index 834c62d7..5d2ccad5 100644 --- a/app/src/main/java/org/fptn/vpn/utils/NetworkUtils.java +++ b/app/src/main/java/org/fptn/vpn/utils/NetworkUtils.java @@ -8,6 +8,7 @@ import org.fptn.vpn.enums.NetworkType; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; @@ -28,7 +29,8 @@ public static String getCurrentIPAddress() { while (inetAddressEnumeration.hasMoreElements()) { InetAddress inetAddress = inetAddressEnumeration.nextElement(); if (!inetAddress.isLoopbackAddress() - && inetAddress instanceof Inet4Address) { //!inetAddress.getHostAddress().contains(":") + && (inetAddress instanceof Inet4Address || inetAddress instanceof Inet6Address)) { + Log.d(TAG, "getCurrentIPAddress(): " + inetAddress.getHostAddress()); return inetAddress.getHostAddress(); } } diff --git a/app/src/main/java/org/fptn/vpn/utils/NotificationUtils.java b/app/src/main/java/org/fptn/vpn/utils/NotificationUtils.java index c7add80b..51beff0d 100644 --- a/app/src/main/java/org/fptn/vpn/utils/NotificationUtils.java +++ b/app/src/main/java/org/fptn/vpn/utils/NotificationUtils.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull; public class NotificationUtils { - public static void configureNotificationChannel(Context context) { + public static void configureNotificationChannels(Context context) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); // add main notification channel diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e7168d1..df573f2b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ activity = "1.10.1" androidGradlePlugin = "8.13.2" androidlombock = "1.18.42" appcompat = "1.7.1" -assertjCore = "3.27.6" +assertjCore = "3.27.7" autonomousappsSorting = "0.15" autonomousappsUnused = "3.6.1" constraintlayout = "2.2.1" @@ -14,8 +14,8 @@ detektPlugin = "1.23.8" firebaseBom = "34.11.0" firebaseCrashlyticsPlugin = "3.0.6" googlePlayServices = "4.4.4" -gson = "2.13.2" -guava = "33.5.0-jre" +gson = "2.14.0" +guava = "33.6.0-jre" ipaddress = "5.6.2" jacksonDatabind = "2.21.2" java = "17" From cdf1d132a78ae8037db26587dfde6a4bb1cd8d19 Mon Sep 17 00:00:00 2001 From: Stas Skokov <7090stas@gmail.com> Date: Wed, 29 Apr 2026 12:02:52 +1000 Subject: [PATCH 2/2] fix subnet --- app/src/main/cpp/libs/fptn | 2 +- app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/cpp/libs/fptn b/app/src/main/cpp/libs/fptn index 9cbfdb3a..4baae524 160000 --- a/app/src/main/cpp/libs/fptn +++ b/app/src/main/cpp/libs/fptn @@ -1 +1 @@ -Subproject commit 9cbfdb3ac88ac274c4c571884687482a2ea1aa23 +Subproject commit 4baae5242c0292730504ce284a85ebab64a75cb8 diff --git a/app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java b/app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java index 8e033763..fa0f67ed 100644 --- a/app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java +++ b/app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java @@ -17,7 +17,7 @@ public enum ConnectionSubnets { LOCAL_TUN_INTERFACE_SUBNET("10.10.0.0", 16, "fd00::", 64), FPTN_SERVER_SUBNET("172.16.0.0", 12, "fc00:1::", 64), LOCAL_SUBNET("192.168.0.0", 16, "::", 128), - ALL_SUBNET("0.0.0.0", 0, "::", 128); + ALL_SUBNET("0.0.0.0", 0, "::", 0); public static final int IP_V4_PREFIX_LENGTH = 32; public static final int IP_V6_PREFIX_LENGTH = 128;