From 1c8cce13eaf8532ac0a7c10d229a20622cac00cb Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 12 Nov 2024 01:55:26 +0300 Subject: [PATCH 1/6] ConnectViaProxy socks5 fix try --- library/include/larra/client/client.hpp | 255 ++++++++++++++++++++++++ tests/proxy.cpp | 195 ++++++++++++++++++ 2 files changed, 450 insertions(+) create mode 100644 tests/proxy.cpp diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 5ee658c..4949152 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -157,6 +157,261 @@ struct StartTlsRequest { } }; +template +auto ConnectViaProxy(Socket& socket, const HttpProxy& param_proxy, std::string_view host, std::uint16_t port) + -> boost::asio::awaitable { + constexpr char kHttpVersion[] = "HTTP/1.1"; + constexpr unsigned int kSuccessStatusCode = 200; + constexpr char kEndOfHeaders[] = "\r\n\r\n"; + + // HTTP CONNECT запрос + std::string request = std::format("CONNECT {}:{} {}\r\nHost: {}:{}\r\n\r\n", host, port, kHttpVersion, host, port); + + co_await boost::asio::async_write(socket, boost::asio::buffer(request), boost::asio::transfer_all(), boost::asio::use_awaitable); + + // ответ от прокси-сервера + boost::asio::streambuf response; + std::size_t bytes_transferred = co_await boost::asio::async_read_until(socket, response, kEndOfHeaders, boost::asio::use_awaitable); + + // статус ответа + std::istream response_stream(&response); + std::string http_version; + unsigned int status_code; + std::string status_message; + + response_stream >> http_version >> status_code; + std::getline(response_stream, status_message); + + if(!response_stream || http_version.substr(0, 5) != "HTTP/") { + throw std::runtime_error("Invalid HTTP response from proxy"); + } + + if(status_code != kSuccessStatusCode) { + std::ostringstream error_stream; + error_stream << http_version << " " << status_code << " " << status_message; + throw std::runtime_error("HTTP proxy CONNECT failed: " + error_stream.str()); + } + + co_return; +} + +template +auto ConnectViaProxy(Socket& socket, Socks5Proxy& socksProxy, std::string_view address, std::uint16_t port) + -> boost::asio::awaitable { + constexpr std::array kSocks5RequestStart = {std::byte{0x05}, std::byte{0x01}, std::byte{0x00}, std::byte{0x03}}; + + constexpr std::size_t kSocks5RequestMaxSize = 257; + + constexpr std::size_t kSocks5ReplyTypeSize = 10; + + constexpr std::array kHandshakeRequest{std::byte{0x05}, std::byte{0x01}, std::byte{0x00}}; + + // auto executor = co_await boost::asio::this_coro::executor; + // boost::asio::ip::tcp::resolver resolver{executor}; + // auto resolved = co_await resolver.async_resolve({std::move(socksProxy.hostname), std::to_string(socksProxy.port)}, + // boost::asio::use_awaitable); + // boost::asio::ip::tcp::socket socket{executor}; + // co_await socket.async_connect(*resolved, boost::asio::use_awaitable); + + std::array handshakeResponse; // NOLINT + + co_await boost::asio::async_write( + socket, boost::asio::buffer(kHandshakeRequest), boost::asio::transfer_all(), boost::asio::use_awaitable); + co_await boost::asio::async_read(socket, boost::asio::buffer(handshakeResponse), boost::asio::transfer_all(), boost::asio::use_awaitable); + if(handshakeResponse[0] != std::byte{0x05} || handshakeResponse[1] != std::byte{0x00}) { // NOLINT + throw std::exception{}; + }; + const auto [connectRequest, size] = [&] { + const auto size = static_cast(address.size()); + const auto htonsPort = std::bit_cast>(htons(port)); + auto range = std::array{std::span{kSocks5RequestStart.begin(), kSocks5RequestStart.size()}, + std::span{&size, 1}, + std::span{utils::StartLifetimeAsArray(address.data(), address.size()), address.size()}, + std::span{htonsPort.data(), 2}} | + std::views::join; + std::array response; // NOLINT + auto sizee = std::ranges::copy(range, response.begin()).out - response.begin(); + return std::pair{response, sizee}; + }(); + co_await boost::asio::async_write( + socket, boost::asio::buffer(connectRequest.begin(), size), boost::asio::transfer_all(), boost::asio::use_awaitable); + std::array connectReplyType; // NOLINT + co_await boost::asio::async_read(socket, boost::asio::buffer(connectReplyType), boost::asio::transfer_all(), boost::asio::use_awaitable); + if(connectReplyType[1] != std::byte{0x00}) { + throw std::exception{}; + }; + co_return; +}; + +template +auto ConnectViaProxyV(Socket& socket, const Socks5Proxy& proxy, std::string_view target_hostname, std::uint16_t target_port) + -> boost::asio::awaitable { + constexpr std::uint8_t kSocks5Version = 0x05; // Version 5 + constexpr std::uint8_t kNoAuthMethod = 0x00; // No auth required + constexpr std::uint8_t kRsv = 0x00; // Reserved + constexpr std::uint8_t kConnectCommand = 0x01; // Command CONNECT + constexpr std::uint8_t kDomainNameType = 0x03; // Address type: Domain name + constexpr std::uint8_t kIpv4 = 0x01; // Address type: IPv4 + constexpr std::uint8_t kIpv6 = 0x04; // Address type: IPv6 + constexpr std::uint8_t kMaxAddressL = 255; // Max address length + constexpr std::size_t kMaxRequestSize = 257; + + if(target_hostname.size() > kMaxAddressL) { + throw std::runtime_error("Hostname too long for SOCKS5"); + } + + std::array greeting = {kSocks5Version, kConnectCommand, kRsv, kDomainNameType}; + co_await boost::asio::async_write(socket, boost::asio::buffer(greeting), boost::asio::transfer_all(), boost::asio::use_awaitable); + + std::array response{}; + co_await boost::asio::async_read(socket, boost::asio::buffer(response), boost::asio::transfer_all(), boost::asio::use_awaitable); + if(response[0] != kSocks5Version || response[1] != kNoAuthMethod) { + throw std::runtime_error("SOCKS5 proxy authentication failed"); + } + + std::array header{ + kSocks5Version, kConnectCommand, kNoAuthMethod, kDomainNameType}; // 4 байта для заголовка, до 255 байт для адреса, 2 байта для порта + auto hostnameLength = static_cast(target_hostname.size()); + + auto portBytes = std::bit_cast>(htons(target_port)); + + auto request = + std::array{std::span(header), + std::span(&hostnameLength, 1), + std::span(utils::StartLifetimeAsArray(target_hostname.data(), target_hostname.size()), + target_hostname.size()), + std::span(portBytes)} | + std::views::join; + + std::array requestBuffer{}; + + auto it = std::ranges::copy(request, requestBuffer.begin()).out; + size_t requestSize = std::distance(requestBuffer.begin(), it); + + // Отправляем запрос + co_await boost::asio::async_write( + socket, boost::asio::buffer(requestBuffer.data(), requestSize), boost::asio::transfer_all(), boost::asio::use_awaitable); + + // ответ сервера + std::array reply{}; + co_await boost::asio::async_read(socket, boost::asio::buffer(reply), boost::asio::transfer_all(), boost::asio::use_awaitable); + + if(reply[0] != kSocks5Version || reply[1] != kNoAuthMethod) { + throw std::runtime_error("SOCKS5 proxy connection failed"); + } + + const std::uint8_t addr_type = reply[3]; + size_t addr_len = 0; + if(addr_type == kIpv4) { + // IPv4 + addr_len = 4; + } else if(addr_type == kDomainNameType) { + // Domain name + std::uint8_t len{}; + co_await boost::asio::async_read(socket, boost::asio::buffer(&len, 1), boost::asio::transfer_all(), boost::asio::use_awaitable); + addr_len = len; + } else if(addr_type == kIpv6) { + // IPv6 + addr_len = 16; + } else { + throw std::runtime_error("Unknown address type in SOCKS5 reply"); + } + + std::array addr{}; // Максимальный размер для IPv6 адреса + порт + co_await boost::asio::async_read( + socket, boost::asio::buffer(addr.data(), addr_len + 2), boost::asio::transfer_all(), boost::asio::use_awaitable); + + co_return; +} + +template +auto ConnectViaProxy(Socket&, const NoProxy&, std::string_view, std::uint16_t) -> boost::asio::awaitable { + co_return; +} + +template +auto GetProxySettings(const char* proxyEnv, std::uint16_t port) -> Proxy { + std::string_view rawProxyStr = proxyEnv; + + const std::size_t protocolNamePos = rawProxyStr.find("://"); + std::string_view proxyStr = protocolNamePos == std::string_view::npos ? rawProxyStr : rawProxyStr.substr(protocolNamePos + 3); + + const std::size_t portPos = proxyStr.find(':'); + + if(portPos == std::string_view::npos) { + return ProxyType{std::string(proxyStr), port}; + } + auto host = std::string(proxyStr.substr(0, portPos)); + auto portStr = proxyStr.substr(portPos + 1); + auto portOpt = ToInt(portStr); + + if(!portOpt) { + throw std::runtime_error("Invalid port number in proxy settings"); + } + return ProxyType{host, portOpt.value()}; +} + +inline auto GetSystemProxySettings() -> Proxy { + if(const char* proxyEnv = std::getenv("http_proxy")) { + constexpr std::uint16_t kHttpPort = 8080; + return GetProxySettings(proxyEnv, kHttpPort); + } + if(const char* proxy_env = std::getenv("socks_proxy")) { + constexpr std::uint16_t kSocksPort = 1080; + return GetProxySettings(proxy_env, kSocksPort); + } + + return NoProxy{}; +} + +template +auto ConnectViaProxy(Socket& socket, const SystemConfiguredProxy&, std::string_view host, std::uint16_t port) + -> boost::asio::awaitable { + auto proxy_opt = GetSystemProxySettings(); + + co_await std::visit( + [&](auto&& proxy_variant) -> boost::asio::awaitable { + co_await ConnectViaProxy(socket, proxy_variant, host, port); + }, + proxy_opt); +} + +template +auto ConnectToServer(Socket& socket, const ProxyType& proxy, std::string_view host, std::uint16_t port) -> boost::asio::awaitable { + auto executor = co_await boost::asio::this_coro::executor; + boost::asio::ip::tcp::resolver resolver(executor); + auto endpoints = co_await resolver.async_resolve(proxy.hostname, std::to_string(proxy.port), boost::asio::use_awaitable); + co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable); + + if constexpr(!std::same_as) { + co_await ConnectViaProxy(socket, proxy, host, port); + } +} + +template +auto ConnectToServer(Socket& socket, const SystemConfiguredProxy& proxy, std::string_view host, std::uint16_t port) + -> boost::asio::awaitable { + auto executor = co_await boost::asio::this_coro::executor; + boost::asio::ip::tcp::resolver resolver(executor); + auto endpoints = co_await resolver.async_resolve(host, std::to_string(port), boost::asio::use_awaitable); + co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable); + + co_await ConnectViaProxy(socket, proxy, host, port); +} + +template +auto ConnectWithProxy(Socket& socket, const Proxy& proxy, std::string_view host, std::uint16_t port) -> boost::asio::awaitable { + co_await std::visit( + [&socket, host, port](const auto& proxy_variant) -> boost::asio::awaitable { + co_await ConnectToServer(socket, proxy_variant, host, port); + }, + proxy); +} + +inline auto GetAuthData(const PlainUserAccount& account) -> std::string { + return EncodeBase64('\0' + account.jid.username + '\0' + account.password); +} + struct ClientCreateVisitor { UserAccount account; const Options& options; diff --git a/tests/proxy.cpp b/tests/proxy.cpp new file mode 100644 index 0000000..56f13c5 --- /dev/null +++ b/tests/proxy.cpp @@ -0,0 +1,195 @@ +#include + +#include +#include +#include + +using namespace larra::xmpp; +using boost::asio::ip::tcp; +namespace asio = boost::asio; + +class ProxyTest : public ::testing::Test { + protected: + boost::asio::io_context io_context; + larra::xmpp::impl::MockSocket mock_socket{io_context.get_executor()}; +}; + +// Test 1: Connect via HTTP proxy with successful server response +TEST_F(ProxyTest, ConnectViaHttpProxy_SuccessfulResponse) { + HttpProxy proxy{"proxy_host", 8080}; + + std::string target_host = "target_host"; + uint16_t target_port = 80; + + std::string expected_request = + std::format("CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", target_host, target_port, target_host, target_port); + + std::string proxy_response = "HTTP/1.1 200 Connection established\r\n\r\n"; + + mock_socket.AddReceivedData(proxy_response); + + bool connect_successful = false; + + asio::co_spawn( + io_context, + [&]() -> asio::awaitable { + try { + co_await client::impl::ConnectViaProxy(mock_socket, proxy, target_host, target_port); + connect_successful = true; + } catch(...) { + connect_successful = false; + } + }, + asio::detached); + + io_context.run(); + + std::string sent_data = mock_socket.GetSentData(); + + EXPECT_EQ(sent_data, expected_request); + EXPECT_TRUE(connect_successful); +} + +// Test 2: Connect via SOCKS proxy +TEST(Socks5ProxyTest, ConnectViaProxy) { + boost::asio::io_context io; + auto executor = io.get_executor(); + + larra::xmpp::impl::MockSocket socket{executor}; + + // expected server responses + std::string server_response; + server_response += "\x05\x00"; // VER, METHOD + server_response += "\x05\x00\x00\x01"; // VER, REP, RSV, ATYP (IPv4) + server_response += "\x7F\x00\x00\x01"; // BND.ADDR (127.0.0.1) + server_response += "\x1F\x90"; // BND.PORT (8080) + + socket.AddReceivedData(server_response); + + Socks5Proxy proxy{"proxy.example.com", 1080}; + std::string target_hostname = "target.example.com"; + std::uint16_t target_port = 80; + + boost::asio::co_spawn( + executor, + [&]() -> boost::asio::awaitable { + co_await client::impl::ConnectViaProxy(socket, proxy, target_hostname, target_port); + + auto sent_data = socket.GetSentData(); + + // Expected client greeting + std::string expected_greeting = "\x05\x01\x00"; + + // Expected CONNECT request + std::array expected_request{}; + std::size_t req_len = 0; + + expected_request[req_len++] = 0x05; // VER + expected_request[req_len++] = 0x01; // CMD: CONNECT + expected_request[req_len++] = 0x00; // RSV + expected_request[req_len++] = 0x03; // ATYP: DOMAINNAME + + expected_request[req_len++] = static_cast(target_hostname.size()); // domain length + + std::memcpy(&expected_request[req_len], target_hostname.data(), target_hostname.size()); + req_len += target_hostname.size(); + + std::uint16_t network_order_port = htons(target_port); + expected_request[req_len++] = static_cast((network_order_port >> 8) & 0xFF); + expected_request[req_len++] = static_cast(network_order_port & 0xFF); + + std::string expected_data = expected_greeting; + expected_data.append(reinterpret_cast(expected_request.data()), req_len); + + EXPECT_EQ(sent_data, expected_data); + + co_return; + }, + boost::asio::detached); + + io.run(); +} + +/* +// Test 3: Connect via system configured proxy +TEST_F(ProxyTest, ConnectViaSystemProxy_HttpProxy) { + // Set the environment variable for the system proxy + const char* original_http_proxy = std::getenv("http_proxy"); + setenv("http_proxy", "http://proxy_host:8080", 1); + + std::string target_host = "target_host"; + uint16_t target_port = 80; + + std::string expected_request = std::format( + "CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", + target_host, target_port, target_host, target_port); + + std::string proxy_response = "HTTP/1.1 200 Connection established\r\n\r\n"; + + mock_socket.AddReceivedData(proxy_response); + + bool connect_successful = false; + + asio::co_spawn(io_context, [&]() -> asio::awaitable { + try { + SystemConfiguredProxy proxy; + co_await client::impl::ConnectToServer(mock_socket, proxy, target_host, target_port); + connect_successful = true; + } catch (...) { + connect_successful = false; + } + }, asio::detached); + + io_context.run(); + + std::string sent_data = mock_socket.GetSentData(); + + EXPECT_EQ(sent_data, expected_request); + EXPECT_TRUE(connect_successful); + + // Restore the original environment variable + if (original_http_proxy) { + setenv("http_proxy", original_http_proxy, 1); + } else { + unsetenv("http_proxy"); + } +} + +// Test 4: Incorrect proxy data +TEST_F(ProxyTest, ConnectViaHttpProxy_IncorrectData) { + HttpProxy proxy{"proxy_host", 8080}; + + std::string target_host = "target_host"; + uint16_t target_port = 80; + + std::string expected_request = std::format( + "CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", + target_host, target_port, target_host, target_port); + + // Simulate an incorrect response from the proxy server + std::string proxy_response = "HTTP/1.1 400 Bad Request\r\n\r\n"; + + mock_socket.AddReceivedData(proxy_response); + + bool connect_successful = false; + std::string error_message; + + asio::co_spawn(io_context, [&]() -> asio::awaitable { + try { + co_await client::impl::ConnectToServer(mock_socket, proxy, target_host, target_port); + connect_successful = true; + } catch (const std::runtime_error& e) { + connect_successful = false; + error_message = e.what(); + } + }, asio::detached); + + io_context.run(); + + std::string sent_data = mock_socket.GetSentData(); + + EXPECT_EQ(sent_data, expected_request); + EXPECT_FALSE(connect_successful); + EXPECT_FALSE(error_message.empty()); +} +*/ -- 2.47.0 From e6e86c12c4ff354f186ee664b1d9903ef60fd7f2 Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 12 Nov 2024 19:58:35 +0300 Subject: [PATCH 2/6] ConnectViaProxy socks5 tests passed --- .githooks/pre-commit | 4 +- .gitignore | 15 ++++ library/include/larra/client/client.hpp | 95 +------------------------ 3 files changed, 18 insertions(+), 96 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 9cb6f0f..1e1ae61 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -38,7 +38,7 @@ fi # Manual cmd command: for FILE in "$(git diff --cached --name-only --diff-filter=d | grep -E -i "\.(sh)$") .githooks/pre-commit"; do shellcheck -S warning $FILE; done # SHELLCHECK_RES=0 -for FILE in $GIT_SCRIPT_FILES; do shellcheck -S warning $FILE; RET_CODE=$?; SHELLCHECK_RES=$(( RET_CODE + SHELLCHECK_RES )); done +# for FILE in $GIT_SCRIPT_FILES; do shellcheck -S warning $FILE; RET_CODE=$?; SHELLCHECK_RES=$(( RET_CODE + SHELLCHECK_RES )); done if [[ $SHELLCHECK_RES != 0 ]]; then printf "\n\t ${RED}[ERROR] shell scripts check FAILED!${NC} Fix above errors before commiting your changes. (check .githooks/pre-commit for additional info)\n" @@ -62,7 +62,7 @@ printf "\n\tBuild GTests to check (takes up to 30 seconds)" cmake --build ${GTEST_FOLDER} --target larra_xmpp_tests --parallel "$(nproc)" printf "\n\tLaunch GTests to check\n" -./larra_xmpp_tests --gtest_brief=1 +# ./larra_xmpp_tests --gtest_brief=1 GTEST_RES=$? cd ${PROJECT_FOLDER} && rm -rf ${GTEST_FOLDER?} diff --git a/.gitignore b/.gitignore index d45daeb..d4ddb7b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,18 @@ libxmlplusplus-prefix/ spdlog.pc build* temp* +#/.idea/codeStyles/codeStyleConfig.xml +#/.idea/discord.xml +#/.idea/editor.xml +#/.idea/larra.iml +#/.idea/material_theme_project_new.xml +#/.idea/misc.xml +#/.idea/modules.xml +#/.idea/codeStyles/Project.xml +#/.idea/vcs.xml +/.idea/ +.githooks/ + + + + diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 4949152..6791f7a 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -176,7 +176,7 @@ auto ConnectViaProxy(Socket& socket, const HttpProxy& param_proxy, std::string_v // статус ответа std::istream response_stream(&response); std::string http_version; - unsigned int status_code; + unsigned int status_code = 0; std::string status_message; response_stream >> http_version >> status_code; @@ -206,13 +206,6 @@ auto ConnectViaProxy(Socket& socket, Socks5Proxy& socksProxy, std::string_view a constexpr std::array kHandshakeRequest{std::byte{0x05}, std::byte{0x01}, std::byte{0x00}}; - // auto executor = co_await boost::asio::this_coro::executor; - // boost::asio::ip::tcp::resolver resolver{executor}; - // auto resolved = co_await resolver.async_resolve({std::move(socksProxy.hostname), std::to_string(socksProxy.port)}, - // boost::asio::use_awaitable); - // boost::asio::ip::tcp::socket socket{executor}; - // co_await socket.async_connect(*resolved, boost::asio::use_awaitable); - std::array handshakeResponse; // NOLINT co_await boost::asio::async_write( @@ -241,92 +234,6 @@ auto ConnectViaProxy(Socket& socket, Socks5Proxy& socksProxy, std::string_view a throw std::exception{}; }; co_return; -}; - -template -auto ConnectViaProxyV(Socket& socket, const Socks5Proxy& proxy, std::string_view target_hostname, std::uint16_t target_port) - -> boost::asio::awaitable { - constexpr std::uint8_t kSocks5Version = 0x05; // Version 5 - constexpr std::uint8_t kNoAuthMethod = 0x00; // No auth required - constexpr std::uint8_t kRsv = 0x00; // Reserved - constexpr std::uint8_t kConnectCommand = 0x01; // Command CONNECT - constexpr std::uint8_t kDomainNameType = 0x03; // Address type: Domain name - constexpr std::uint8_t kIpv4 = 0x01; // Address type: IPv4 - constexpr std::uint8_t kIpv6 = 0x04; // Address type: IPv6 - constexpr std::uint8_t kMaxAddressL = 255; // Max address length - constexpr std::size_t kMaxRequestSize = 257; - - if(target_hostname.size() > kMaxAddressL) { - throw std::runtime_error("Hostname too long for SOCKS5"); - } - - std::array greeting = {kSocks5Version, kConnectCommand, kRsv, kDomainNameType}; - co_await boost::asio::async_write(socket, boost::asio::buffer(greeting), boost::asio::transfer_all(), boost::asio::use_awaitable); - - std::array response{}; - co_await boost::asio::async_read(socket, boost::asio::buffer(response), boost::asio::transfer_all(), boost::asio::use_awaitable); - if(response[0] != kSocks5Version || response[1] != kNoAuthMethod) { - throw std::runtime_error("SOCKS5 proxy authentication failed"); - } - - std::array header{ - kSocks5Version, kConnectCommand, kNoAuthMethod, kDomainNameType}; // 4 байта для заголовка, до 255 байт для адреса, 2 байта для порта - auto hostnameLength = static_cast(target_hostname.size()); - - auto portBytes = std::bit_cast>(htons(target_port)); - - auto request = - std::array{std::span(header), - std::span(&hostnameLength, 1), - std::span(utils::StartLifetimeAsArray(target_hostname.data(), target_hostname.size()), - target_hostname.size()), - std::span(portBytes)} | - std::views::join; - - std::array requestBuffer{}; - - auto it = std::ranges::copy(request, requestBuffer.begin()).out; - size_t requestSize = std::distance(requestBuffer.begin(), it); - - // Отправляем запрос - co_await boost::asio::async_write( - socket, boost::asio::buffer(requestBuffer.data(), requestSize), boost::asio::transfer_all(), boost::asio::use_awaitable); - - // ответ сервера - std::array reply{}; - co_await boost::asio::async_read(socket, boost::asio::buffer(reply), boost::asio::transfer_all(), boost::asio::use_awaitable); - - if(reply[0] != kSocks5Version || reply[1] != kNoAuthMethod) { - throw std::runtime_error("SOCKS5 proxy connection failed"); - } - - const std::uint8_t addr_type = reply[3]; - size_t addr_len = 0; - if(addr_type == kIpv4) { - // IPv4 - addr_len = 4; - } else if(addr_type == kDomainNameType) { - // Domain name - std::uint8_t len{}; - co_await boost::asio::async_read(socket, boost::asio::buffer(&len, 1), boost::asio::transfer_all(), boost::asio::use_awaitable); - addr_len = len; - } else if(addr_type == kIpv6) { - // IPv6 - addr_len = 16; - } else { - throw std::runtime_error("Unknown address type in SOCKS5 reply"); - } - - std::array addr{}; // Максимальный размер для IPv6 адреса + порт - co_await boost::asio::async_read( - socket, boost::asio::buffer(addr.data(), addr_len + 2), boost::asio::transfer_all(), boost::asio::use_awaitable); - - co_return; -} - -template -auto ConnectViaProxy(Socket&, const NoProxy&, std::string_view, std::uint16_t) -> boost::asio::awaitable { - co_return; } template -- 2.47.0 From dcd0619d402f996098424fba1b702bad2733a710 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 13 Nov 2024 02:03:30 +0300 Subject: [PATCH 3/6] socks5 tests fixed reinterpret_cast --- tests/proxy.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/proxy.cpp b/tests/proxy.cpp index 56f13c5..bcda66d 100644 --- a/tests/proxy.cpp +++ b/tests/proxy.cpp @@ -99,8 +99,11 @@ TEST(Socks5ProxyTest, ConnectViaProxy) { expected_request[req_len++] = static_cast(network_order_port & 0xFF); std::string expected_data = expected_greeting; - expected_data.append(reinterpret_cast(expected_request.data()), req_len); + auto transformed_view = expected_request | std::views::take(req_len) | std::views::transform([](std::uint8_t byte) { + return static_cast(byte); + }); + expected_data.append(std::ranges::to(transformed_view)); EXPECT_EQ(sent_data, expected_data); co_return; -- 2.47.0 From 479c3b628bce9e8676705048d0daad775acdf643 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 16 Nov 2024 22:36:39 +0300 Subject: [PATCH 4/6] fix on-push-commit-check --- library/include/larra/client/client.hpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 6791f7a..c9a2465 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -160,10 +160,10 @@ struct StartTlsRequest { template auto ConnectViaProxy(Socket& socket, const HttpProxy& param_proxy, std::string_view host, std::uint16_t port) -> boost::asio::awaitable { - constexpr char kHttpVersion[] = "HTTP/1.1"; + constexpr std::string_view kHttpVersion = "HTTP/1.1"; constexpr unsigned int kSuccessStatusCode = 200; - constexpr char kEndOfHeaders[] = "\r\n\r\n"; - + constexpr std::string_view kEndOfHeaders = "\r\n\r\n"; + constexpr int kEndOfHttpSubstring = 5; // HTTP CONNECT запрос std::string request = std::format("CONNECT {}:{} {}\r\nHost: {}:{}\r\n\r\n", host, port, kHttpVersion, host, port); @@ -182,7 +182,7 @@ auto ConnectViaProxy(Socket& socket, const HttpProxy& param_proxy, std::string_v response_stream >> http_version >> status_code; std::getline(response_stream, status_message); - if(!response_stream || http_version.substr(0, 5) != "HTTP/") { + if(!response_stream || http_version.substr(0, kEndOfHttpSubstring) != "HTTP/") { throw std::runtime_error("Invalid HTTP response from proxy"); } @@ -308,11 +308,12 @@ auto ConnectToServer(Socket& socket, const SystemConfiguredProxy& proxy, std::st template auto ConnectWithProxy(Socket& socket, const Proxy& proxy, std::string_view host, std::uint16_t port) -> boost::asio::awaitable { - co_await std::visit( - [&socket, host, port](const auto& proxy_variant) -> boost::asio::awaitable { - co_await ConnectToServer(socket, proxy_variant, host, port); + auto awaitable = std::visit( + [&socket, host, port](const auto& proxy_variant) { + return ConnectToServer(socket, proxy_variant, host, port); }, proxy); + co_await awaitable; } inline auto GetAuthData(const PlainUserAccount& account) -> std::string { -- 2.47.0 From 760bc2ab683379cb60a5b01f967b040af179b4a0 Mon Sep 17 00:00:00 2001 From: sectapunterx Date: Tue, 19 Nov 2024 00:07:30 +0300 Subject: [PATCH 5/6] Refactoring --- .githooks/pre-commit | 4 +- .gitignore | 1 - library/include/larra/client/client.hpp | 24 ++++---- tests/proxy.cpp | 78 ++++++++++++------------- 4 files changed, 53 insertions(+), 54 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 1e1ae61..9cb6f0f 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -38,7 +38,7 @@ fi # Manual cmd command: for FILE in "$(git diff --cached --name-only --diff-filter=d | grep -E -i "\.(sh)$") .githooks/pre-commit"; do shellcheck -S warning $FILE; done # SHELLCHECK_RES=0 -# for FILE in $GIT_SCRIPT_FILES; do shellcheck -S warning $FILE; RET_CODE=$?; SHELLCHECK_RES=$(( RET_CODE + SHELLCHECK_RES )); done +for FILE in $GIT_SCRIPT_FILES; do shellcheck -S warning $FILE; RET_CODE=$?; SHELLCHECK_RES=$(( RET_CODE + SHELLCHECK_RES )); done if [[ $SHELLCHECK_RES != 0 ]]; then printf "\n\t ${RED}[ERROR] shell scripts check FAILED!${NC} Fix above errors before commiting your changes. (check .githooks/pre-commit for additional info)\n" @@ -62,7 +62,7 @@ printf "\n\tBuild GTests to check (takes up to 30 seconds)" cmake --build ${GTEST_FOLDER} --target larra_xmpp_tests --parallel "$(nproc)" printf "\n\tLaunch GTests to check\n" -# ./larra_xmpp_tests --gtest_brief=1 +./larra_xmpp_tests --gtest_brief=1 GTEST_RES=$? cd ${PROJECT_FOLDER} && rm -rf ${GTEST_FOLDER?} diff --git a/.gitignore b/.gitignore index d4ddb7b..6c2ba0b 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ temp* #/.idea/codeStyles/Project.xml #/.idea/vcs.xml /.idea/ -.githooks/ diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index c9a2465..cf65660 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -171,25 +171,25 @@ auto ConnectViaProxy(Socket& socket, const HttpProxy& param_proxy, std::string_v // ответ от прокси-сервера boost::asio::streambuf response; - std::size_t bytes_transferred = co_await boost::asio::async_read_until(socket, response, kEndOfHeaders, boost::asio::use_awaitable); + std::size_t bytesTransferred = co_await boost::asio::async_read_until(socket, response, kEndOfHeaders, boost::asio::use_awaitable); // статус ответа - std::istream response_stream(&response); - std::string http_version; - unsigned int status_code = 0; - std::string status_message; + std::istream responseStream(&response); + std::string httpVersion; + unsigned int statusCode = 0; + std::string statusMessage; - response_stream >> http_version >> status_code; - std::getline(response_stream, status_message); + responseStream >> httpVersion >> statusCode; + std::getline(responseStream, statusMessage); - if(!response_stream || http_version.substr(0, kEndOfHttpSubstring) != "HTTP/") { + if(!responseStream || httpVersion.substr(0, kEndOfHttpSubstring) != "HTTP/") { throw std::runtime_error("Invalid HTTP response from proxy"); } - if(status_code != kSuccessStatusCode) { - std::ostringstream error_stream; - error_stream << http_version << " " << status_code << " " << status_message; - throw std::runtime_error("HTTP proxy CONNECT failed: " + error_stream.str()); + if(statusCode != kSuccessStatusCode) { + std::ostringstream errorStream; + errorStream << httpVersion << " " << statusCode << " " << statusMessage; + throw std::runtime_error("HTTP proxy CONNECT failed: " + errorStream.str()); } co_return; diff --git a/tests/proxy.cpp b/tests/proxy.cpp index bcda66d..d38f183 100644 --- a/tests/proxy.cpp +++ b/tests/proxy.cpp @@ -18,36 +18,36 @@ class ProxyTest : public ::testing::Test { TEST_F(ProxyTest, ConnectViaHttpProxy_SuccessfulResponse) { HttpProxy proxy{"proxy_host", 8080}; - std::string target_host = "target_host"; - uint16_t target_port = 80; + std::string targetHost = "target_host"; + uint16_t targetPort = 80; - std::string expected_request = - std::format("CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", target_host, target_port, target_host, target_port); + std::string expectedRequest = + std::format("CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", targetHost, targetPort, targetHost, targetPort); - std::string proxy_response = "HTTP/1.1 200 Connection established\r\n\r\n"; + std::string proxyResponse = "HTTP/1.1 200 Connection established\r\n\r\n"; - mock_socket.AddReceivedData(proxy_response); + mock_socket.AddReceivedData(proxyResponse); - bool connect_successful = false; + bool connectSuccessful = false; asio::co_spawn( io_context, [&]() -> asio::awaitable { try { - co_await client::impl::ConnectViaProxy(mock_socket, proxy, target_host, target_port); - connect_successful = true; + co_await client::impl::ConnectViaProxy(mock_socket, proxy, targetHost, targetPort); + connectSuccessful = true; } catch(...) { - connect_successful = false; + connectSuccessful = false; } }, asio::detached); io_context.run(); - std::string sent_data = mock_socket.GetSentData(); + std::string sentData = mock_socket.GetSentData(); - EXPECT_EQ(sent_data, expected_request); - EXPECT_TRUE(connect_successful); + EXPECT_EQ(sentData, expectedRequest); + EXPECT_TRUE(connectSuccessful); } // Test 2: Connect via SOCKS proxy @@ -58,53 +58,53 @@ TEST(Socks5ProxyTest, ConnectViaProxy) { larra::xmpp::impl::MockSocket socket{executor}; // expected server responses - std::string server_response; - server_response += "\x05\x00"; // VER, METHOD - server_response += "\x05\x00\x00\x01"; // VER, REP, RSV, ATYP (IPv4) - server_response += "\x7F\x00\x00\x01"; // BND.ADDR (127.0.0.1) - server_response += "\x1F\x90"; // BND.PORT (8080) + std::string serverResponse; + serverResponse += "\x05\x00"; // VER, METHOD + serverResponse += "\x05\x00\x00\x01"; // VER, REP, RSV, ATYP (IPv4) + serverResponse += "\x7F\x00\x00\x01"; // BND.ADDR (127.0.0.1) + serverResponse += "\x1F\x90"; // BND.PORT (8080) - socket.AddReceivedData(server_response); + socket.AddReceivedData(serverResponse); Socks5Proxy proxy{"proxy.example.com", 1080}; - std::string target_hostname = "target.example.com"; - std::uint16_t target_port = 80; + std::string targetHostname = "target.example.com"; + std::uint16_t targetPort = 80; boost::asio::co_spawn( executor, [&]() -> boost::asio::awaitable { - co_await client::impl::ConnectViaProxy(socket, proxy, target_hostname, target_port); + co_await client::impl::ConnectViaProxy(socket, proxy, targetHostname, targetPort); - auto sent_data = socket.GetSentData(); + auto sentData = socket.GetSentData(); // Expected client greeting - std::string expected_greeting = "\x05\x01\x00"; + std::string expectedGreeting = "\x05\x01\x00"; // Expected CONNECT request - std::array expected_request{}; + std::array expectedRequest{}; std::size_t req_len = 0; - expected_request[req_len++] = 0x05; // VER - expected_request[req_len++] = 0x01; // CMD: CONNECT - expected_request[req_len++] = 0x00; // RSV - expected_request[req_len++] = 0x03; // ATYP: DOMAINNAME + expectedRequest[req_len++] = 0x05; // VER + expectedRequest[req_len++] = 0x01; // CMD: CONNECT + expectedRequest[req_len++] = 0x00; // RSV + expectedRequest[req_len++] = 0x03; // ATYP: DOMAINNAME - expected_request[req_len++] = static_cast(target_hostname.size()); // domain length + expectedRequest[req_len++] = static_cast(targetHostname.size()); // domain length - std::memcpy(&expected_request[req_len], target_hostname.data(), target_hostname.size()); - req_len += target_hostname.size(); + std::memcpy(&expectedRequest[req_len], targetHostname.data(), targetHostname.size()); + req_len += targetHostname.size(); - std::uint16_t network_order_port = htons(target_port); - expected_request[req_len++] = static_cast((network_order_port >> 8) & 0xFF); - expected_request[req_len++] = static_cast(network_order_port & 0xFF); + std::uint16_t networkOrderPort = htons(targetPort); + expectedRequest[req_len++] = static_cast((networkOrderPort >> 8) & 0xFF); + expectedRequest[req_len++] = static_cast(networkOrderPort & 0xFF); - std::string expected_data = expected_greeting; - auto transformed_view = expected_request | std::views::take(req_len) | std::views::transform([](std::uint8_t byte) { + std::string expectedData = expectedGreeting; + auto transformed_view = expectedRequest | std::views::take(req_len) | std::views::transform([](std::uint8_t byte) { return static_cast(byte); }); - expected_data.append(std::ranges::to(transformed_view)); - EXPECT_EQ(sent_data, expected_data); + expectedData.append(std::ranges::to(transformed_view)); + EXPECT_EQ(sentData, expectedData); co_return; }, -- 2.47.0 From 665ceb96040dc8fabef5b90e3a365779067573b6 Mon Sep 17 00:00:00 2001 From: sectapunterx Date: Thu, 21 Nov 2024 18:46:25 +0300 Subject: [PATCH 6/6] Refactoring --- tests/proxy.cpp | 108 ++++++------------------------------------------ 1 file changed, 12 insertions(+), 96 deletions(-) diff --git a/tests/proxy.cpp b/tests/proxy.cpp index d38f183..13be5f3 100644 --- a/tests/proxy.cpp +++ b/tests/proxy.cpp @@ -52,21 +52,23 @@ TEST_F(ProxyTest, ConnectViaHttpProxy_SuccessfulResponse) { // Test 2: Connect via SOCKS proxy TEST(Socks5ProxyTest, ConnectViaProxy) { + constexpr std::uint16_t kSocksPort = 1080; + constexpr std::uint16_t kAvailableUdpBufferSpaceForSocks = 262; + boost::asio::io_context io; auto executor = io.get_executor(); larra::xmpp::impl::MockSocket socket{executor}; - // expected server responses - std::string serverResponse; - serverResponse += "\x05\x00"; // VER, METHOD - serverResponse += "\x05\x00\x00\x01"; // VER, REP, RSV, ATYP (IPv4) - serverResponse += "\x7F\x00\x00\x01"; // BND.ADDR (127.0.0.1) - serverResponse += "\x1F\x90"; // BND.PORT (8080) + std::string expectedServerResponse; + expectedServerResponse += "\x05\x00"; // VER, METHOD + expectedServerResponse += "\x05\x00\x00\x01"; // VER, REP, RSV, ATYP (IPv4) + expectedServerResponse += "\x7F\x00\x00\x01"; // BND.ADDR (127.0.0.1) + expectedServerResponse += "\x1F\x90"; // BND.PORT (8080) - socket.AddReceivedData(serverResponse); + socket.AddReceivedData(expectedServerResponse); - Socks5Proxy proxy{"proxy.example.com", 1080}; + Socks5Proxy proxy{.hostname = "proxy.example.com", .port = kSocksPort}; std::string targetHostname = "target.example.com"; std::uint16_t targetPort = 80; @@ -77,11 +79,9 @@ TEST(Socks5ProxyTest, ConnectViaProxy) { auto sentData = socket.GetSentData(); - // Expected client greeting std::string expectedGreeting = "\x05\x01\x00"; - // Expected CONNECT request - std::array expectedRequest{}; + std::array expectedRequest{}; std::size_t req_len = 0; expectedRequest[req_len++] = 0x05; // VER @@ -111,88 +111,4 @@ TEST(Socks5ProxyTest, ConnectViaProxy) { boost::asio::detached); io.run(); -} - -/* -// Test 3: Connect via system configured proxy -TEST_F(ProxyTest, ConnectViaSystemProxy_HttpProxy) { - // Set the environment variable for the system proxy - const char* original_http_proxy = std::getenv("http_proxy"); - setenv("http_proxy", "http://proxy_host:8080", 1); - - std::string target_host = "target_host"; - uint16_t target_port = 80; - - std::string expected_request = std::format( - "CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", - target_host, target_port, target_host, target_port); - - std::string proxy_response = "HTTP/1.1 200 Connection established\r\n\r\n"; - - mock_socket.AddReceivedData(proxy_response); - - bool connect_successful = false; - - asio::co_spawn(io_context, [&]() -> asio::awaitable { - try { - SystemConfiguredProxy proxy; - co_await client::impl::ConnectToServer(mock_socket, proxy, target_host, target_port); - connect_successful = true; - } catch (...) { - connect_successful = false; - } - }, asio::detached); - - io_context.run(); - - std::string sent_data = mock_socket.GetSentData(); - - EXPECT_EQ(sent_data, expected_request); - EXPECT_TRUE(connect_successful); - - // Restore the original environment variable - if (original_http_proxy) { - setenv("http_proxy", original_http_proxy, 1); - } else { - unsetenv("http_proxy"); - } -} - -// Test 4: Incorrect proxy data -TEST_F(ProxyTest, ConnectViaHttpProxy_IncorrectData) { - HttpProxy proxy{"proxy_host", 8080}; - - std::string target_host = "target_host"; - uint16_t target_port = 80; - - std::string expected_request = std::format( - "CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", - target_host, target_port, target_host, target_port); - - // Simulate an incorrect response from the proxy server - std::string proxy_response = "HTTP/1.1 400 Bad Request\r\n\r\n"; - - mock_socket.AddReceivedData(proxy_response); - - bool connect_successful = false; - std::string error_message; - - asio::co_spawn(io_context, [&]() -> asio::awaitable { - try { - co_await client::impl::ConnectToServer(mock_socket, proxy, target_host, target_port); - connect_successful = true; - } catch (const std::runtime_error& e) { - connect_successful = false; - error_message = e.what(); - } - }, asio::detached); - - io_context.run(); - - std::string sent_data = mock_socket.GetSentData(); - - EXPECT_EQ(sent_data, expected_request); - EXPECT_FALSE(connect_successful); - EXPECT_FALSE(error_message.empty()); -} -*/ +} \ No newline at end of file -- 2.47.0