From f41a7acf2a5d24820a2dbabea043e484830d3d3f Mon Sep 17 00:00:00 2001 From: astracat Date: Sun, 15 Mar 2026 04:15:13 +0300 Subject: [PATCH] Optimize performance for 1Gbps+ networks --- CMakeLists.txt | 4 +- deploy/docker/Dockerfile.sourcebuild | 84 +++++++++++++ docker-compose/docker-compose.yml | 2 +- src/common/data/channel.h | 9 +- src/common/data/channel_async.h | 62 +++++----- src/common/utils/utils.h | 20 ++- .../obfuscator/methods/tls/tls_obfuscator.cpp | 114 ++++++++---------- .../websocket_client/websocket_client.cpp | 68 +++++++---- src/fptn-protocol-lib/protobuf/protocol.cpp | 20 ++- .../traffic_shaper/leaky_bucket.cpp | 13 +- src/fptn-server/web/listener/listener.cpp | 13 ++ src/fptn-server/web/session/session.cpp | 17 ++- 12 files changed, 280 insertions(+), 146 deletions(-) create mode 100644 deploy/docker/Dockerfile.sourcebuild diff --git a/CMakeLists.txt b/CMakeLists.txt index e1115599..420f8cfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,9 +42,9 @@ else() endif() add_compile_definitions(FPTN_VERSION=\"${FPTN_VERSION}\") -add_compile_definitions(FPTN_MTU_SIZE=1400) +add_compile_definitions(FPTN_MTU_SIZE=1450) add_compile_definitions(FPTN_DEFAULT_SNI=\"rutube.ru\") -add_compile_definitions(FPTN_IP_PACKET_MAX_SIZE=1300) +add_compile_definitions(FPTN_IP_PACKET_MAX_SIZE=1400) add_compile_definitions(FPTN_ENABLE_PACKET_PADDING=1) add_compile_definitions(FPTN_PROTOBUF_PROTOCOL_VERSION=0x01) # client diff --git a/deploy/docker/Dockerfile.sourcebuild b/deploy/docker/Dockerfile.sourcebuild new file mode 100644 index 00000000..03838489 --- /dev/null +++ b/deploy/docker/Dockerfile.sourcebuild @@ -0,0 +1,84 @@ +FROM ubuntu:24.04 AS builder + +ARG FPTN_VERSION=local + +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Etc/UTC +ENV CONAN_HOME=/tmp/conan +ENV PIP_BREAK_SYSTEM_PACKAGES=1 + +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y \ + autoconf \ + automake \ + build-essential \ + ca-certificates \ + cmake \ + curl \ + git \ + libelf-dev \ + libpcap-dev \ + libtool \ + m4 \ + meson \ + ninja-build \ + perl \ + pkg-config \ + python3 \ + python3-pip \ + unzip \ + wget \ + zip && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN pip3 install --no-cache-dir conan==2.24.0 + +WORKDIR /src +COPY . . + +RUN sed -i "s/^FPTN_VERSION = \".*\"/FPTN_VERSION = \"${FPTN_VERSION}\"/" conanfile.py && \ + conan profile detect --force && \ + conan install . \ + --output-folder=build \ + --build=missing \ + -o with_gui_client=False \ + --settings build_type=Release \ + -s compiler.cppstd=17 && \ + cmake -S . -B build \ + -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release && \ + cmake --build build --config Release --target fptn-server fptn-passwd -j"$(nproc)" + +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Etc/UTC + +EXPOSE 443/tcp + +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y unbound iproute2 iptables supervisor wget dnsmasq && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /etc/supervisor/conf.d + +COPY --from=builder /src/build/src/fptn-server/fptn-server /usr/local/bin/fptn-server +COPY --from=builder /src/build/src/fptn-passwd/fptn-passwd /usr/local/bin/fptn-passwd +RUN chmod +x /usr/local/bin/fptn-server && \ + chmod +x /usr/local/bin/fptn-passwd + +COPY deploy/docker/config/supervisord.conf /etc/supervisor/supervisord.conf +COPY deploy/docker/config/supervisor/*.conf /etc/supervisor/conf.d/ + +COPY deploy/docker/scripts/start-fptn.sh /usr/local/bin/start-fptn.sh +COPY deploy/docker/scripts/start-dns-server.sh /usr/local/bin/start-dns-server.sh +COPY deploy/docker/scripts/token-generator.py /usr/local/bin/token-generator +RUN chmod +x /usr/local/bin/start-fptn.sh && \ + chmod +x /usr/local/bin/token-generator && \ + chmod +x /usr/local/bin/start-dns-server.sh + +CMD ["/usr/bin/supervisord", "--nodaemon", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 1b603eb2..4baf9244 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,7 +1,7 @@ services: fptn-server: restart: unless-stopped - image: fptnvpn/fptn-vpn-server:latest + image: astracat/fptn-vpn-server:latest cap_add: - NET_ADMIN - SYS_MODULE diff --git a/src/common/data/channel.h b/src/common/data/channel.h index 0779c452..b6a7b1fb 100644 --- a/src/common/data/channel.h +++ b/src/common/data/channel.h @@ -23,14 +23,17 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT) namespace fptn::common::data { class Channel { public: - explicit Channel(std::size_t maxCapacity = 512) { + // Increase default capacity for high throughput + explicit Channel(std::size_t maxCapacity = 8192) { buffer_.set_capacity(maxCapacity); } void Push(network::IPPacketPtr pkt) noexcept { { const std::unique_lock lock(mutex_); // mutex - - buffer_.push_back(std::move(pkt)); + if (buffer_.size() < buffer_.capacity()) { + buffer_.push_back(std::move(pkt)); + } + // If buffer full, drop packet (better than unbounded growth or blocking) } condvar_.notify_one(); } diff --git a/src/common/data/channel_async.h b/src/common/data/channel_async.h index b14d2338..47d88f39 100644 --- a/src/common/data/channel_async.h +++ b/src/common/data/channel_async.h @@ -32,18 +32,25 @@ using boost::asio::use_awaitable; class ChannelAsync { public: - explicit ChannelAsync(boost::asio::io_context& ioc, - std::size_t maxCapacity = 512, - std::size_t threadPoolSize = 4) - : ioc_(ioc), pool_(threadPoolSize) { - buffer_.set_capacity(maxCapacity); + // Increased defaults for high throughput + explicit ChannelAsync(boost::asio::io_context& ioc) + : ioc_(ioc) { + // Capacity 8192 + buffer_.set_capacity(8192); } void Push(network::IPPacketPtr pkt) { { const std::unique_lock lock(mutex_); - buffer_.push_back(std::move(pkt)); + if (buffer_.size() < buffer_.capacity()) { + buffer_.push_back(std::move(pkt)); + } } condvar_.notify_one(); + + // Trigger async waiter if any + if (timer_) { + timer_->cancel(); + } } network::IPPacketPtr WaitForPacket( @@ -67,11 +74,10 @@ class ChannelAsync { } boost::asio::awaitable> - WaitForPacketAsync(const std::chrono::milliseconds& duration) { + WaitForPacketAsync() { + // Optimistic check without creating timer { - const std::unique_lock lock(mutex_); // mutex - - // exists + const std::unique_lock lock(mutex_); if (!buffer_.empty()) { auto pkt = std::move(buffer_.front()); buffer_.pop_front(); @@ -79,12 +85,18 @@ class ChannelAsync { } } - // wait for timeout - co_await AsyncWaitUntil(duration); - + // Wait for notification or timeout (e.g. 50ms to batch) + try { + timer_ = std::make_unique(ioc_); + timer_->expires_after(std::chrono::milliseconds(50)); + co_await timer_->async_wait(boost::asio::use_awaitable); + } catch (...) { + // Timer cancelled means data arrived + } + + // Check again { - const std::unique_lock lock(mutex_); // mutex - + const std::unique_lock lock(mutex_); if (!buffer_.empty()) { auto pkt = std::move(buffer_.front()); buffer_.pop_front(); @@ -94,27 +106,9 @@ class ChannelAsync { co_return std::nullopt; } - boost::asio::awaitable AsyncWaitUntil( - const std::chrono::steady_clock::duration& timeout) { - boost::asio::steady_timer timer(ioc_, timeout); - - co_await timer.async_wait(boost::asio::use_awaitable); - - // while (!mutex_.try_lock()) { - // if (std::chrono::steady_clock::now() - start > - // std::chrono::seconds(3)) { - // spdlog::error("Session::send: failed to acquire lock within - // timeout"); co_return false; - // } - // std::this_thread::yield(); // Yield to avoid busy waiting - // } - - co_return; - } - protected: boost::asio::io_context& ioc_; - boost::asio::thread_pool pool_; + std::unique_ptr timer_; mutable std::mutex mutex_; std::condition_variable condvar_; diff --git a/src/common/utils/utils.h b/src/common/utils/utils.h index 56da81a5..00bf00b4 100644 --- a/src/common/utils/utils.h +++ b/src/common/utils/utils.h @@ -7,6 +7,7 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT) #pragma once #include +#include #include #include #include @@ -20,14 +21,31 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT) #include namespace fptn::common::utils { + +inline void GenerateRandomBytes(std::uint8_t* buffer, std::size_t length) { + static thread_local std::mt19937 gen{std::random_device{}()}; + std::uniform_int_distribution dist; + + std::size_t i = 0; + for (; i + 8 <= length; i += 8) { + uint64_t rand_val = dist(gen); + std::memcpy(buffer + i, &rand_val, 8); + } + if (i < length) { + uint64_t rand_val = dist(gen); + std::memcpy(buffer + i, &rand_val, length - i); + } +} + inline std::string GenerateRandomString(int length) { const std::string characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - std::mt19937 gen{std::random_device {}()}; + static thread_local std::mt19937 gen{std::random_device {}()}; std::uniform_int_distribution dist(0, characters.size() - 1); std::string result; + result.reserve(length); for (int i = 0; i < length; i++) { result += characters[dist(gen)]; } diff --git a/src/fptn-protocol-lib/https/obfuscator/methods/tls/tls_obfuscator.cpp b/src/fptn-protocol-lib/https/obfuscator/methods/tls/tls_obfuscator.cpp index d1b876fd..afa5e258 100644 --- a/src/fptn-protocol-lib/https/obfuscator/methods/tls/tls_obfuscator.cpp +++ b/src/fptn-protocol-lib/https/obfuscator/methods/tls/tls_obfuscator.cpp @@ -62,31 +62,33 @@ std::uint16_t NetworkToHost16(const std::uint16_t value) { } std::uint64_t GetRandomData() { - static std::mt19937 gen{std::random_device {}()}; - std::uniform_int_distribution dist(1024, UINT64_MAX); + static thread_local std::mt19937 gen{std::random_device{}()}; + std::uniform_int_distribution dist; return dist(gen); } std::uint8_t GetRandomByte( const std::uint8_t min = 0, const std::uint8_t max = UINT8_MAX) { - static std::mt19937 gen{std::random_device {}()}; + static thread_local std::mt19937 gen{std::random_device{}()}; std::uniform_int_distribution dist(min, max); return static_cast(dist(gen)); } -std::vector GenerateRandomPadding(const std::size_t length) { - std::vector padding(length); - for (std::size_t i = 0; i < length; ++i) { - padding[i] = GetRandomByte(); - } - return padding; -} - -void ApplyXorTransform( - std::uint8_t* data, const std::size_t size, const std::uint8_t key) { - for (std::size_t i = 0; i < size; ++i) { - data[i] ^= key; - } +void GenerateRandomPadding(std::uint8_t* buffer, std::size_t length) { + static thread_local std::mt19937 gen{std::random_device{}()}; + std::uniform_int_distribution dist; + + std::size_t i = 0; + // Fill 8 bytes at a time + for (; i + 8 <= length; i += 8) { + uint64_t rand_val = dist(gen); + std::memcpy(buffer + i, &rand_val, 8); + } + // Fill remaining bytes + if (i < length) { + uint64_t rand_val = dist(gen); + std::memcpy(buffer + i, &rand_val, length - i); + } } } // namespace @@ -167,21 +169,21 @@ PreparedData TlsObfuscator::Deobfuscate() { const std::uint8_t* encrypted_payload = input_buffer_.data() + search_offset + sizeof(TLSAppDataRecordHeader); - // Copy encrypted payload to temporary buffer for XOR processing - std::vector decrypted_payload( - encrypted_payload, encrypted_payload + payload_length); - - // Apply XOR decryption - ApplyXorTransform( - decrypted_payload.data(), decrypted_payload.size(), header.xor_key); - - // Add decrypted payload to output - output.insert( - output.end(), decrypted_payload.begin(), decrypted_payload.end()); + // Decrypt directly into output + if (payload_length > 0) { + std::size_t current_output_size = output.size(); + output.resize(current_output_size + payload_length); + std::uint8_t* dest = output.data() + current_output_size; + + for (std::size_t i = 0; i < payload_length; ++i) { + dest[i] = encrypted_payload[i] ^ header.xor_key; + } + } - // Remove the processed record from buffer starting from search_offset - input_buffer_.erase(input_buffer_.begin() + search_offset, + // Remove garbage + processed record from buffer + input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + search_offset + full_record_size); + total_processed += full_record_size; break; } @@ -203,17 +205,10 @@ PreparedData TlsObfuscator::Obfuscate( const std::uint8_t* data, std::size_t size) { // Generate random padding (0-255 bytes) const std::uint8_t padding_length = GetRandomByte(64, 255); - std::vector random_padding = - GenerateRandomPadding(padding_length); // Generate XOR key const std::uint8_t xor_key = GetRandomByte(); - // Prepare payload for XOR encryption - std::vector encrypted_payload(data, data + size); - ApplyXorTransform( - encrypted_payload.data(), encrypted_payload.size(), xor_key); - const std::uint16_t total_content_length = sizeof(TLSAppDataRecordHeader::random_data) + sizeof(TLSAppDataRecordHeader::magic_flag) + @@ -223,42 +218,37 @@ PreparedData TlsObfuscator::Obfuscate( sizeof(TLSAppDataRecordHeader::padding_length) + static_cast(size) + padding_length; - TLSAppDataRecordHeader header = {}; - header.headertype = kFptnTlsApplicationHeaderType; - header.headermajor = kFptnTlsApplicationHeaderMajor; - header.headerminor = kFptnTlsApplicationHeaderMinor; - - // Convert to network byte order - header.content_length = HostToNetwork16(total_content_length); - header.random_data = GetRandomData(); - header.magic_flag = HostToNetwork16(kFptnTlsApplicationMagicFlag); - header.protocol_version = kFptnTlsApplicationProtocolVersion; - header.xor_key = xor_key; - header.payload_length = HostToNetwork16(static_cast(size)); - header.padding_length = padding_length; - std::vector result; result.resize(sizeof(TLSAppDataRecordHeader) + size + padding_length); - // Copy header - std::memcpy(result.data(), &header, sizeof(TLSAppDataRecordHeader)); + TLSAppDataRecordHeader* header = reinterpret_cast(result.data()); + header->headertype = kFptnTlsApplicationHeaderType; + header->headermajor = kFptnTlsApplicationHeaderMajor; + header->headerminor = kFptnTlsApplicationHeaderMinor; - // Copy encrypted payload + // Convert to network byte order + header->content_length = HostToNetwork16(total_content_length); + header->random_data = GetRandomData(); + header->magic_flag = HostToNetwork16(kFptnTlsApplicationMagicFlag); + header->protocol_version = kFptnTlsApplicationProtocolVersion; + header->xor_key = xor_key; + header->payload_length = HostToNetwork16(static_cast(size)); + header->padding_length = padding_length; + + // Copy encrypted payload directly into result if (size > 0) { - std::memcpy(result.data() + sizeof(TLSAppDataRecordHeader), - encrypted_payload.data(), size); + std::uint8_t* payload_dest = result.data() + sizeof(TLSAppDataRecordHeader); + for (std::size_t i = 0; i < size; ++i) { + payload_dest[i] = data[i] ^ xor_key; + } } - // Copy random padding + // Generate random padding directly into result if (padding_length > 0) { - std::memcpy(result.data() + sizeof(TLSAppDataRecordHeader) + size, - random_padding.data(), padding_length); + GenerateRandomPadding(result.data() + sizeof(TLSAppDataRecordHeader) + size, padding_length); } - if (!result.empty()) { - return result; - } - return std::nullopt; + return result; } void TlsObfuscator::Reset() { input_buffer_.clear(); } diff --git a/src/fptn-protocol-lib/https/websocket_client/websocket_client.cpp b/src/fptn-protocol-lib/https/websocket_client/websocket_client.cpp index aa6d9d1e..db7e1161 100644 --- a/src/fptn-protocol-lib/https/websocket_client/websocket_client.cpp +++ b/src/fptn-protocol-lib/https/websocket_client/websocket_client.cpp @@ -52,6 +52,9 @@ WebsocketClient::WebsocketClient(fptn::common::network::IPv4Address server_ip, https::utils::SetHandshakeSni(ssl, sni_); https::utils::SetHandshakeSessionID(ssl); + // Set SSL buffer sizes + SSL_set_mode(ssl, SSL_MODE_RELEASE_BUFFERS); + if (censorship_strategy_ == CensorshipStrategy::kSni) { obfuscator_ = nullptr; } @@ -81,9 +84,23 @@ WebsocketClient::WebsocketClient(fptn::common::network::IPv4Address server_ip, ws_.text(false); ws_.binary(true); ws_.auto_fragment(true); - ws_.read_message_max(128 * 1024); + ws_.read_message_max(4 * 1024 * 1024); // Increase max message size ws_.set_option(boost::beast::websocket::stream_base::timeout::suggested( boost::beast::role_type::client)); + + // Optimize socket buffer sizes + try { + boost::beast::get_lowest_layer(ws_).socket().set_option( + boost::asio::socket_base::receive_buffer_size(4 * 1024 * 1024)); + boost::beast::get_lowest_layer(ws_).socket().set_option( + boost::asio::socket_base::send_buffer_size(4 * 1024 * 1024)); + + // Disable Nagle's algorithm for lower latency + boost::beast::get_lowest_layer(ws_).socket().set_option( + boost::asio::ip::tcp::no_delay(true)); + } catch(const boost::system::system_error& e) { + SPDLOG_WARN("Failed to set socket options: {}", e.what()); + } } WebsocketClient::~WebsocketClient() { @@ -128,15 +145,8 @@ void WebsocketClient::Run() { }, boost::asio::detached); try { - while (running_ || !was_stopped_) { - const std::size_t processed = ioc_.poll_one(); - if (processed == 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - } - if (!ioc_.stopped()) { - ioc_.stop(); - } + ioc_.restart(); + ioc_.run(); } catch (...) { SPDLOG_WARN("Exception while running"); } @@ -327,8 +337,17 @@ boost::asio::awaitable WebsocketClient::Connect() { SPDLOG_INFO("Connected to {}:{}", server_ip_.ToString(), server_port_str_); // TCP options - boost::beast::get_lowest_layer(ws_).socket().set_option( - boost::asio::ip::tcp::no_delay(true)); + auto& socket = boost::beast::get_lowest_layer(ws_).socket(); + socket.set_option(boost::asio::ip::tcp::no_delay(true)); + + // Optimize socket buffers + try { + const int buffer_size = 4 * 1024 * 1024; + socket.set_option(boost::asio::socket_base::receive_buffer_size(buffer_size)); + socket.set_option(boost::asio::socket_base::send_buffer_size(buffer_size)); + } catch (...) { + SPDLOG_WARN("Failed to set socket buffer sizes in Connect()"); + } // Reality Mode: Enhanced stealth connection protocol // First, establishes a genuine TLS handshake as a decoy to bypass deep @@ -518,22 +537,19 @@ boost::asio::awaitable WebsocketClient::PerformFakeHandshake() { SPDLOG_ERROR("Failed to send fake handshake: {}", ec.message()); co_return false; } - - SPDLOG_INFO("Successfully sent {} bytes of handshake data", bytes_sent); - - do { - std::array buffer{}; - const std::size_t bytes_read = co_await tcp_socket.async_receive( - boost::asio::buffer(buffer), boost::asio::use_awaitable); - if (ec && ec != boost::asio::error::eof) { + + // Read response + std::array buffer; + const std::size_t bytes_read = co_await tcp_socket.async_receive( + boost::asio::buffer(buffer), + boost::asio::redirect_error(boost::asio::use_awaitable, ec)); + + if (ec && ec != boost::asio::error::eof) { SPDLOG_WARN("Read during fake handshake failed: {}", ec.message()); - } - if (bytes_read) { - break; - } - } while (true); + co_return false; + } - SPDLOG_INFO("Fake handshake completed successfully"); + SPDLOG_INFO("Fake handshake completed successfully, read {} bytes", bytes_read); co_return true; } catch (const std::exception& e) { SPDLOG_ERROR("PerformFakeHandshake exception: {}", e.what()); diff --git a/src/fptn-protocol-lib/protobuf/protocol.cpp b/src/fptn-protocol-lib/protobuf/protocol.cpp index 189e7d44..11a9ae71 100644 --- a/src/fptn-protocol-lib/protobuf/protocol.cpp +++ b/src/fptn-protocol-lib/protobuf/protocol.cpp @@ -30,8 +30,7 @@ std::string GetProtoPayload(const std::string& raw) { switch (message.msg_type()) { case fptn::protocol::MSG_IP_PACKET: if (message.has_packet()) { - const auto& payload = message.packet().payload(); - return std::string(payload.data(), payload.size()); + return std::move(*message.mutable_packet()->mutable_payload()); } throw ProcessingError("Malformed IP packet."); case fptn::protocol::MSG_ERROR: @@ -66,17 +65,15 @@ std::string CreateProtoPayload(fptn::common::network::IPPacketPtr packet) { if (max_padding > 0) { static thread_local std::mt19937 gen{std::random_device {}()}; - static thread_local std::uniform_int_distribution dist( - 0, kMaxPaddingBytes); + std::uniform_int_distribution dist(0, max_padding); - const std::size_t padding_size = dist(gen) % (max_padding + 1); + const std::size_t padding_size = dist(gen); if (padding_size > 0) { std::string padding_buffer; padding_buffer.resize(padding_size); - auto* gen_ptr = &gen; - std::ranges::generate(padding_buffer, - [gen_ptr]() { return static_cast((*gen_ptr)() & 0xFF); }); + fptn::common::utils::GenerateRandomBytes( + reinterpret_cast(padding_buffer.data()), padding_size); message.mutable_packet()->set_padding_data( padding_buffer.data(), padding_size); @@ -85,10 +82,9 @@ std::string CreateProtoPayload(fptn::common::network::IPPacketPtr packet) { } #endif const std::size_t estimated_size = message.ByteSizeLong(); - std::string serialized_data; - serialized_data.reserve(estimated_size + 32); - - if (!message.SerializeToString(&serialized_data)) { + std::string serialized_data(estimated_size, '\0'); + if (!message.SerializeToArray( + serialized_data.data(), static_cast(estimated_size))) { SPDLOG_ERROR("Failed to serialize Message."); return {}; } diff --git a/src/fptn-server/traffic_shaper/leaky_bucket.cpp b/src/fptn-server/traffic_shaper/leaky_bucket.cpp index 5b75e155..651de102 100644 --- a/src/fptn-server/traffic_shaper/leaky_bucket.cpp +++ b/src/fptn-server/traffic_shaper/leaky_bucket.cpp @@ -12,13 +12,24 @@ LeakyBucket::LeakyBucket(std::size_t max_bites_per_second) : current_amount_(0), max_bytes_per_second_(max_bites_per_second / 8), last_leak_time_(std::chrono::steady_clock::now()), - full_data_amount_(0) {} + full_data_amount_(0) { + // If max_bites_per_second is very large or 0, treat it as unlimited + if (max_bites_per_second >= 10000000000ULL) { // > 10 Gbps + max_bytes_per_second_ = 0; + } +} std::size_t LeakyBucket::FullDataAmount() const noexcept { return full_data_amount_; } bool LeakyBucket::CheckSpeedLimit(std::size_t packet_size) noexcept { + if (max_bytes_per_second_ == 0) { // No limit + std::unique_lock lock(mutex_); + full_data_amount_ += packet_size; + return true; + } + const std::unique_lock lock(mutex_); // mutex auto now = std::chrono::steady_clock::now(); diff --git a/src/fptn-server/web/listener/listener.cpp b/src/fptn-server/web/listener/listener.cpp index bef097a3..0ce7b3c7 100644 --- a/src/fptn-server/web/listener/listener.cpp +++ b/src/fptn-server/web/listener/listener.cpp @@ -71,6 +71,13 @@ boost::asio::awaitable Listener::Run() { acceptor_.open(endpoint_.protocol()); acceptor_.set_option(boost::asio::ip::tcp::no_delay(true)); acceptor_.set_option(boost::asio::socket_base::reuse_address(true)); + + // Optimize socket buffers for high throughput (1 Gbit/s+) + // Set send/recv buffers to 4MB (typical for high-speed WAN) + const int buffer_size = 4 * 1024 * 1024; + acceptor_.set_option(boost::asio::socket_base::receive_buffer_size(buffer_size)); + acceptor_.set_option(boost::asio::socket_base::send_buffer_size(buffer_size)); + acceptor_.bind(endpoint_); acceptor_.listen(boost::asio::socket_base::max_listen_connections); } catch (boost::system::system_error& err) { @@ -85,7 +92,13 @@ boost::asio::awaitable Listener::Run() { boost::asio::ip::tcp::socket socket(ioc_); co_await acceptor_.async_accept( socket, boost::asio::redirect_error(boost::asio::use_awaitable, ec)); + if (!ec) { + // Propagate buffer settings to the accepted socket + socket.set_option(boost::asio::ip::tcp::no_delay(true)); + socket.set_option(boost::asio::socket_base::receive_buffer_size(buffer_size)); + socket.set_option(boost::asio::socket_base::send_buffer_size(buffer_size)); + auto session = std::make_shared(port_, // probing settings enable_detect_probing_, default_proxy_domain_, allowed_sni_list_, diff --git a/src/fptn-server/web/session/session.cpp b/src/fptn-server/web/session/session.cpp index e225f903..12c5eafd 100644 --- a/src/fptn-server/web/session/session.cpp +++ b/src/fptn-server/web/session/session.cpp @@ -95,7 +95,7 @@ Session::Session(std::uint16_t port, ws_(ssl_stream_type( obfuscator_socket_type(tcp_stream_type(std::move(socket))), ctx)), strand_(boost::asio::make_strand(ws_.get_executor())), - write_channel_(strand_, 128), + write_channel_(strand_, 8192), api_handles_(api_handles), handshake_cache_manager_(std::move(handshake_cache_manager)), ws_open_callback_(std::move(ws_open_callback)), @@ -256,11 +256,16 @@ boost::asio::awaitable Session::DetectProbing() { auto& tcp_socket = boost::beast::get_lowest_layer(ws_).socket(); // Peek data without consuming it from the socket buffer!!! // This allows inspection without affecting subsequent reads!!! - std::array buffer{}; + std::array buffer; + + // Set socket timeout for peek + boost::system::error_code ec; const std::size_t bytes_read = co_await tcp_socket.async_receive(boost::asio::buffer(buffer), - boost::asio::socket_base::message_peek, boost::asio::use_awaitable); - if (!bytes_read) { + boost::asio::socket_base::message_peek, + boost::asio::redirect_error(boost::asio::use_awaitable, ec)); + + if (ec || !bytes_read) { SPDLOG_ERROR("Peeked zero bytes from socket (client_id={})", client_id_); co_return ProbingResult{.is_probing = true, .sni = default_proxy_domain_, @@ -290,6 +295,10 @@ boost::asio::awaitable Session::DetectProbing() { // Check handshake // https://github.com/wiresock/ndisapi/blob/master/examples/cpp/pcapplusplus/pcapplusplus.cpp#L40 const auto* handshake = dynamic_cast(ssl_layer); + + // Cleanup memory! + std::unique_ptr ssl_layer_ptr(ssl_layer); + if (!handshake) { SPDLOG_ERROR("Failed to cast to SSLHandshakeLayer"); co_return ProbingResult{.is_probing = true,