Skip to content
Merged
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
2 changes: 1 addition & 1 deletion app/src/main/cpp/libs/fptn
Submodule fptn updated from 9cbfdb to 4baae5
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
52 changes: 34 additions & 18 deletions app/src/main/java/org/fptn/vpn/enums/ConnectionSubnets.java
Original file line number Diff line number Diff line change
Expand Up @@ -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, "::", 0);

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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
120 changes: 83 additions & 37 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,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;
Expand All @@ -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;
Expand All @@ -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;
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,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<IPAddress> 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<IPAddress> 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<IPAddress> subnetsToInclude = new ArrayList<>();
IPUtils.exclude(rootSubnet, subnetsToExclude, subnetsToInclude);
for (IPAddress ipAddress : subnetsToInclude) {
List<IPAddress> 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<IPAddress> 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<IPAddress> 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();
Expand Down
Loading