Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions app/src/main/cpp/src/websocket_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<jlong>(websocket_client);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)),
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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_;
Expand Down
61 changes: 45 additions & 16 deletions app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,58 @@

@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: fix IPv6
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);

// todo: rename me!
HZ_WHAT_IS_THIS_IP("172.20.0.1", 32);
private final String ipV4Address;
private final int v4prefix;

private final String ipAddress;
private final int prefix;
private final String ipV6Address;
private final int v6prefix;

ConnectionSubnets(String ipAddress, int prefix) {
this.ipAddress = ipAddress;
this.prefix = prefix;
ConnectionSubnets(String ipV4Address, int v4prefix, String ipV6Address, int v6prefix) {
this.ipV4Address = ipV4Address;
this.v4prefix = v4prefix;
this.ipV6Address = ipV6Address;
this.v6prefix = v6prefix;
}

@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
public IpPrefix getAsIpPrefix() throws UnknownHostException {
return new IpPrefix(InetAddress.getByName(ipAddress), prefix);
public IpPrefix getAsIpV4Prefix() throws UnknownHostException {
return new IpPrefix(InetAddress.getByName(ipV4Address), v4prefix);
}

public String getAsIPWithPrefix() {
return ipAddress + "/" + prefix;
public String getAsIpV4PrefixAsString() {
return ipV4Address + "/" + v4prefix;
}

@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
public IpPrefix getAsIpV6Prefix() throws UnknownHostException {
return new IpPrefix(InetAddress.getByName(ipV6Address), v6prefix);
}

public String getAsIpV6PrefixAsString() {
return ipV6Address + "/" + v6prefix;
}

public String getIpV4Address() {
return ipV4Address;
}

public String getIpV6Address() {
return ipV6Address;
}

public int getIpV4Prefix() {
return v4prefix;
}

public int getIpV6Prefix() {
return v6prefix;
}

}
77 changes: 45 additions & 32 deletions app/src/main/java/org/fptn/vpn/services/vpn/FptnConnection.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
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.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;
Expand All @@ -20,6 +19,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;
Expand All @@ -32,6 +32,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;
Expand Down Expand Up @@ -61,6 +62,8 @@ public class FptnConnection extends Thread {
* Maximum packet size is constrained by the MTU
*/
private static final int MAX_PACKET_SIZE = 1500;
public static final int IP_V4_PREFIX_LENGTH = 32;
public static final int IP_V6_PREFIX_LENGTH = 128;

@Getter
private final int connectionId;
Expand Down Expand Up @@ -112,7 +115,7 @@ public FptnConnection(final FptnService service,
final String sniHostName,
final BypassCensorshipMethod censorshipStrategy,
final PerAppVpnMode perAppVpnMode,
final List<AppInfo> appInfos) {
final List<AppInfo> appInfos) throws UnknownHostException {
this.service = service;
this.connectionId = connectionId;
this.serverEntity = serverEntity;
Expand All @@ -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;
}
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -171,22 +175,33 @@ 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.getIpV4Prefix());
builder.addRoute(dns_server.getIpv4(), 32);

// IPv6
builder.addDnsServer(dns_server.getIpv6());
builder.addAddress(LOCAL_TUN_ADDRESS.getIpV6Address(), LOCAL_TUN_ADDRESS.getIpV6Prefix());
builder.addRoute(dns_server.getIpv6(), 128);

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());
builder.excludeRoute(new IpPrefix(inetAddress, IP_V4_PREFIX_LENGTH));
builder.excludeRoute(LOCAL_TUN_INTERFACE_SUBNET.getAsIpV4Prefix());
builder.excludeRoute(FPTN_SERVER_SUBNET.getAsIpV4Prefix());
builder.excludeRoute(LOCAL_SUBNET.getAsIpV4Prefix());
builder.addRoute(ALL_SUBNET.getIpV4Address(), ALL_SUBNET.getV4prefix());
} else {
IPAddress rootSubnet = new IPAddressString(ALL_SUBNET.getAsIPWithPrefix()).getAddress();
IPAddress rootSubnet = new IPAddressString(ALL_SUBNET.getAsIpV4PrefixAsString()).getAddress();
List<IPAddress> subnetsToExclude = Stream.of(
serverEntity.getHost() + "/32",
TUN_INTERFACE_SUBNET.getAsIPWithPrefix(),
FPTN_SUBNET.getAsIPWithPrefix(),
LOCAL_SUBNET.getAsIPWithPrefix()
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());
Expand All @@ -197,12 +212,10 @@ public void run() {
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);
builder.addRoute(hostIp, networkPrefixLength != null ? networkPrefixLength : IP_V4_PREFIX_LENGTH);
}
}

builder.setSession(serverEntity.getName()).setConfigureIntent(configureVpnIntent);

synchronized (service) {
vpnInterface = builder.establish();
}
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/java/org/fptn/vpn/services/vpn/FptnService.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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;
Expand Down Expand Up @@ -284,7 +285,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()));
}
});
Expand Down Expand Up @@ -414,7 +416,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(), "");

Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/org/fptn/vpn/services/websocket/DnsServers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.fptn.vpn.services.websocket;

public class DnsServers {
private final String ipv4;
private final String ipv6;

public DnsServers(String ipv4, String ipv6) {
this.ipv4 = ipv4;
this.ipv6 = ipv6;
}

public String getIpv4() { return ipv4; }
public String getIpv6() { return ipv6; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -53,7 +54,8 @@ public NativeWebSocketClientImpl(
this.nativeHandle = nativeCreate(
host,
port,
tunAddress,
tunAddressIPv4,
tunAddressIPv6,
sniHostName,
accessToken,
md5ServerFingerprint,
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading