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
6 changes: 4 additions & 2 deletions deploy/docker/scripts/start-fptn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ echo "[FPTN] Using network interface: $OUT_NETWORK_INTERFACE"
exec /usr/local/bin/fptn-server \
--server-key=/etc/fptn/server.key \
--server-crt=/etc/fptn/server.crt \
--out-network-interface="$OUT_NETWORK_INTERFACE" \
--out-network-interface="${OUT_NETWORK_INTERFACE}" \
--server-port=443 \
--enable-detect-probing="$ENABLE_DETECT_PROBING" \
--enable-detect-probing="${ENABLE_DETECT_PROBING}" \
--default-proxy-domain="${DEFAULT_PROXY_DOMAIN}" \
--allowed-sni-list="${ALLOWED_SNI_LIST}" \
--tun-interface-name=fptn0 \
--disable-bittorrent="$DISABLE_BITTORRENT" \
--prometheus-access-key="$PROMETHEUS_SECRET_ACCESS_KEY" \
Expand Down
17 changes: 16 additions & 1 deletion deploy/linux/deb/create-server-deb-package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,20 @@ TUN_INTERFACE_NAME=fptn0
# Enable detection of probing attempts (experimental; accepted values: true or false)
ENABLE_DETECT_PROBING=false

# true or false
# Default domain where non-VPN client traffic will be redirected
# When someone scans your server (not using VPN), their connection will be forwarded to this domain instead
DEFAULT_PROXY_DOMAIN=cdnvideo.com

# Comma-separated list of allowed website domains for non-VPN clients
# This acts like a "whitelist" of websites that scanning bots are allowed to reach
# Behavior logic:
# - List is empty (default): proxy all non-VPN traffic to DEFAULT_PROXY_DOMAIN
# - List is NOT empty: use as whitelist:
# - Client SNI in list -> proxy to client's SNI\
# - Client SNI not in list -> proxy to --default-proxy-domain
ALLOWED_SNI_LIST=

# Block BitTorrent traffic to prevent abuse (accepted values: true or false)
DISABLE_BITTORRENT=true

# Set the USE_REMOTE_SERVER_AUTH variable to true if you need to
Expand Down Expand Up @@ -90,6 +103,8 @@ ExecStart=/usr/bin/$(basename "$SERVER_BIN") \
--out-network-interface=\${OUT_NETWORK_INTERFACE} \
--server-port=\${PORT} \
--enable-detect-probing=\${ENABLE_DETECT_PROBING} \
--default-proxy-domain=\${DEFAULT_PROXY_DOMAIN} \
--allowed-sni-list=\${ALLOWED_SNI_LIST} \
--tun-interface-name=\${TUN_INTERFACE_NAME} \
--disable-bittorrent=\${DISABLE_BITTORRENT} \
--prometheus-access-key=\${PROMETHEUS_SECRET_ACCESS_KEY} \
Expand Down
15 changes: 14 additions & 1 deletion docker-compose/.env.demo
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,20 @@ SERVER_EXTERNAL_IPS=
# Enable detection of probing attempts (accepted values: true or false)
ENABLE_DETECT_PROBING=true

# true or false
# Default domain where non-VPN client traffic will be redirected
# When someone scans your server (not using VPN), their connection will be forwarded to this domain instead
DEFAULT_PROXY_DOMAIN=cdnvideo.com

# Comma-separated list of allowed website domains for non-VPN clients
# This acts like a "whitelist" of websites that scanning bots are allowed to reach
# Behavior logic:
# - List is empty (default): proxy all non-VPN traffic to DEFAULT_PROXY_DOMAIN
# - List is NOT empty: use as whitelist:
# - Client SNI in list -> proxy to client's SNI\
# - Client SNI not in list -> proxy to --default-proxy-domain
ALLOWED_SNI_LIST=

# Block BitTorrent traffic to prevent abuse (accepted values: true or false)
DISABLE_BITTORRENT=true

# Set the USE_REMOTE_SERVER_AUTH variable to true if you need to
Expand Down
2 changes: 2 additions & 0 deletions docker-compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ services:
- ./fptn-server-data:/etc/fptn
environment:
- ENABLE_DETECT_PROBING=${ENABLE_DETECT_PROBING}
- DEFAULT_PROXY_DOMAIN=${DEFAULT_PROXY_DOMAIN}
- ALLOWED_SNI_LIST=${ALLOWED_SNI_LIST}
- DISABLE_BITTORRENT=${DISABLE_BITTORRENT}
- PROMETHEUS_SECRET_ACCESS_KEY=${PROMETHEUS_SECRET_ACCESS_KEY}
- USE_REMOTE_SERVER_AUTH=${USE_REMOTE_SERVER_AUTH}
Expand Down
40 changes: 39 additions & 1 deletion src/fptn-server/config/command_line_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT)
#include <algorithm>
#include <memory>
#include <string>
#include <vector>

#include <spdlog/spdlog.h> // NOLINT(build/include_order)

#include "common/utils/utils.h"

namespace {
bool ParseBoolean(std::string value) noexcept {
try {
Expand Down Expand Up @@ -105,11 +108,26 @@ CommandLineConfig::CommandLineConfig(int argc, char* argv[])
.help("Maximum number of active sessions allowed per VPN user")
.default_value(3)
.scan<'i', int>();
// Probing
args_.add_argument("--enable-detect-probing")
.help(
"Enable detection of non-FPTN clients or probing attempts during SSL "
"handshake. ")
.default_value("false");
args_.add_argument("--default-proxy-domain")
.help("Default domain for proxying non-VPN clients.")
.default_value(FPTN_DEFAULT_SNI);
args_.add_argument("--allowed-sni-list")
.help(
"Comma-separated list of allowed SNI hostnames for non-VPN clients.\n"
"Behavior logic:\n"
" - List is empty (default): proxy all non-VPN traffic to "
"--default-proxy-domain\n"
" - List is NOT empty: use as whitelist:\n"
" - Client SNI in list -> proxy to client's SNI\n"
" - Client SNI not in list -> proxy to --default-proxy-domain")
.default_value("");
// Prevent self-proxy
args_.add_argument("--server-external-ips")
.help(
"Public IPv4 address of this VPN server. "
Expand Down Expand Up @@ -204,12 +222,32 @@ bool CommandLineConfig::EnableDetectProbing() const {
return ParseBoolean(args_.get<std::string>("--enable-detect-probing"));
}

[[nodiscard]]
std::string CommandLineConfig::DefaultProxyDomain() const {
auto default_domain = args_.get<std::string>("--default-proxy-domain");
if (default_domain.empty()) {
return FPTN_DEFAULT_SNI;
}
return default_domain;
}

[[nodiscard]]
std::vector<std::string> CommandLineConfig::AllowedSniList() const {
const auto allowed_sni = args_.get<std::string>("--allowed-sni-list");
if (!allowed_sni.empty()) {
return common::utils::SplitCommaSeparated(
allowed_sni + "," + DefaultProxyDomain());
}
return {};
}

std::size_t CommandLineConfig::MaxActiveSessionsPerUser() const {
return static_cast<std::size_t>(
args_.get<int>("--max-active-sessions-per-user"));
}

[[nodiscard]] std::string CommandLineConfig::ServerExternalIPs() const {
[[nodiscard]]
std::string CommandLineConfig::ServerExternalIPs() const {
return args_.get<std::string>("--server-external-ips");
}

Expand Down
4 changes: 4 additions & 0 deletions src/fptn-server/config/command_line_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT)
#pragma once

#include <string>
#include <vector>

#include <argparse/argparse.hpp> // NOLINT(build/include_order)

Expand Down Expand Up @@ -49,6 +50,9 @@ class CommandLineConfig {

[[nodiscard]] bool EnableDetectProbing() const;

[[nodiscard]] std::string DefaultProxyDomain() const;
[[nodiscard]] std::vector<std::string> AllowedSniList() const;

[[nodiscard]] std::size_t MaxActiveSessionsPerUser() const;

[[nodiscard]] std::string ServerExternalIPs() const;
Expand Down
19 changes: 17 additions & 2 deletions src/fptn-server/fptn-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT)
#include <utility>

#include <boost/asio.hpp>
#include <fmt/ranges.h> // NOLINT(build/include_order)

#include "common/jwt_token/token_manager.h"
#include "common/logger/logger.h"
Expand Down Expand Up @@ -105,8 +106,13 @@ int main(int argc, char* argv[]) {
auto web_server = std::make_unique<fptn::web::Server>(config.ServerPort(),
nat_table, user_manager, token_manager, prometheus,
config.PrometheusAccessKey(), config.TunInterfaceIPv4(),
config.TunInterfaceIPv6(), config.EnableDetectProbing(),
config.TunInterfaceIPv6(),
/* probing */
config.EnableDetectProbing(), config.DefaultProxyDomain(),
config.AllowedSniList(),
/* sessions */
config.MaxActiveSessionsPerUser(),
/* External IPs */
config.ServerExternalIPs());

/* init packet filter */
Expand All @@ -131,11 +137,19 @@ int main(int argc, char* argv[]) {
"VPN NETWORK IPv6: {}\n"
"VPN SERVER PORT: {}\n"
"DETECT_PROBING: {}\n"
"DEFAULT_PROXY_DOMAIN: {}\n"
"ALLOWED_SNI_LIST: {}\n"
"MAX_ACTIVE_SESSIONS_PER_USER: {}\n",
FPTN_VERSION, config.OutNetworkInterface(),
FPTN_VERSION,
// Network settings
config.OutNetworkInterface(),
config.TunInterfaceNetworkIPv4Address().ToString(),
config.TunInterfaceNetworkIPv6Address().ToString(), config.ServerPort(),
// Probing settings
config.EnableDetectProbing() ? "YES" : "NO",
config.DefaultProxyDomain(),
fmt::format("[{}]", fmt::join(config.AllowedSniList(), ", ")),
// max session
config.MaxActiveSessionsPerUser());

// Init vpn manager
Expand All @@ -147,6 +161,7 @@ int main(int argc, char* argv[]) {
manager.Start();
WaitForSignal();
manager.Stop();

return EXIT_SUCCESS;
} catch (const std::exception& ex) {
SPDLOG_ERROR("An error occurred: {}. Exiting...", ex.what());
Expand Down
14 changes: 11 additions & 3 deletions src/fptn-server/web/listener/listener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT)
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
Expand All @@ -24,6 +25,8 @@ using fptn::web::Listener;

Listener::Listener(std::uint16_t port,
bool enable_detect_probing,
std::string default_proxy_domain,
std::vector<std::string> allowed_sni_list,
boost::asio::io_context& ioc,
fptn::common::jwt_token::TokenManagerSPtr token_manager,
HandshakeCacheManagerSPtr handshake_cache_manager,
Expand All @@ -33,6 +36,8 @@ Listener::Listener(std::uint16_t port,
WebSocketCloseConnectionCallback ws_close_callback)
: port_(port),
enable_detect_probing_(enable_detect_probing),
default_proxy_domain_(std::move(default_proxy_domain)),
allowed_sni_list_(std::move(allowed_sni_list)),
ioc_(ioc),
ctx_(boost::asio::ssl::context::tlsv13_server),
acceptor_(ioc_),
Expand Down Expand Up @@ -81,9 +86,12 @@ boost::asio::awaitable<void> Listener::Run() {
co_await acceptor_.async_accept(
socket, boost::asio::redirect_error(boost::asio::use_awaitable, ec));
if (!ec) {
auto session = std::make_shared<Session>(port_, enable_detect_probing_,
server_external_ips_, std::move(socket), ctx_, api_handles_,
handshake_cache_manager_, ws_open_callback_,
auto session = std::make_shared<Session>(port_,
// probing settings
enable_detect_probing_, default_proxy_domain_, allowed_sni_list_,
server_external_ips_, std::move(socket), ctx_,
// handlers
api_handles_, handshake_cache_manager_, ws_open_callback_,
ws_new_ippacket_callback_, ws_close_callback_);
// run coroutine
boost::asio::co_spawn(
Expand Down
5 changes: 5 additions & 0 deletions src/fptn-server/web/listener/listener.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT)
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
Expand All @@ -27,6 +28,8 @@ class Listener final {
public:
explicit Listener(std::uint16_t port,
bool enable_detect_probing,
std::string default_proxy_domain,
std::vector<std::string> allowed_sni_list,
boost::asio::io_context& ioc,
fptn::common::jwt_token::TokenManagerSPtr token_manager,
HandshakeCacheManagerSPtr handshake_cache_manager,
Expand All @@ -44,6 +47,8 @@ class Listener final {
protected:
const std::uint16_t port_;
const bool enable_detect_probing_;
const std::string default_proxy_domain_;
const std::vector<std::string> allowed_sni_list_;

boost::asio::io_context& ioc_;
boost::asio::ssl::context ctx_;
Expand Down
12 changes: 10 additions & 2 deletions src/fptn-server/web/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT)
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <spdlog/spdlog.h> // NOLINT(build/include_order)

Expand All @@ -27,6 +28,8 @@ Server::Server(std::uint16_t port,
fptn::common::network::IPv4Address dns_server_ipv4,
fptn::common::network::IPv6Address dns_server_ipv6,
bool enable_detect_probing,
std::string default_proxy_domain,
std::vector<std::string> allowed_sni_list,
std::size_t max_active_sessions_per_user,
std::string server_external_ips,
int thread_number)
Expand All @@ -40,6 +43,8 @@ Server::Server(std::uint16_t port,
dns_server_ipv4_(std::move(dns_server_ipv4)),
dns_server_ipv6_(std::move(dns_server_ipv6)),
enable_detect_probing_(enable_detect_probing),
default_proxy_domain_(std::move(default_proxy_domain)),
allowed_sni_list_(std::move(allowed_sni_list)),
max_active_sessions_per_user_(max_active_sessions_per_user),
server_external_ips_(std::move(server_external_ips)),
thread_number_(std::max<std::size_t>(1, thread_number)),
Expand All @@ -56,8 +61,11 @@ Server::Server(std::uint16_t port,

handshake_cache_manager_ = std::make_shared<HandshakeCacheManager>(ioc_);

listener_ = std::make_shared<Listener>(port_, enable_detect_probing_, ioc_,
token_manager, handshake_cache_manager_, server_external_ips_,
listener_ = std::make_shared<Listener>(port_,
// proxy settings
enable_detect_probing_, default_proxy_domain_, allowed_sni_list_,
// ioc
ioc_, token_manager, handshake_cache_manager_, server_external_ips_,
// NOLINTNEXTLINE(modernize-avoid-bind)
std::bind(
&Server::HandleWsOpenConnection, this, _1, _2, _3, _4, _5, _6, _7),
Expand Down
5 changes: 5 additions & 0 deletions src/fptn-server/web/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class Server final {
fptn::common::network::IPv4Address dns_server_ipv4,
fptn::common::network::IPv6Address dns_server_ipv6,
bool enable_detect_probing,
std::string default_proxy_domain,
std::vector<std::string> allowed_sni_list,
std::size_t max_active_sessions_per_user,
std::string server_external_ips,
int thread_number = 4);
Expand Down Expand Up @@ -91,6 +93,9 @@ class Server final {
const fptn::common::network::IPv4Address dns_server_ipv4_;
const fptn::common::network::IPv6Address dns_server_ipv6_;
const bool enable_detect_probing_;
const std::string default_proxy_domain_;
const std::vector<std::string> allowed_sni_list_;

const std::size_t max_active_sessions_per_user_;
const std::string server_external_ips_;
const std::size_t thread_number_;
Expand Down
Loading