Asynchronously connect to a proxy server and make an HTTP request to the application server:
File: socks_client_async.cpp
#include <boost/socks/connect.hpp> #include <boost/socks/connect_v4.hpp> #include <boost/url/url.hpp> #include <boost/beast/core.hpp> #include <boost/beast/http.hpp> #include <boost/beast/version.hpp> #include <boost/asio/connect.hpp> #include <boost/asio/read.hpp> #include <boost/asio/write.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/udp.hpp> #include <cstdlib> #include <iostream> #include <string> #include <utility> namespace socks = boost::socks; namespace urls = boost::urls; namespace beast = boost::beast; namespace http = beast::http; namespace asio = boost::asio; namespace ip = boost::asio::ip; using tcp = boost::asio::ip::tcp; using string_view = socks::string_view; using error_code = socks::error_code; using endpoint = socks::endpoint; //------------------------------------------------------------------------------ // A small version of std::to_string to avoid a // common bug on MinGw. std::string to_string(std::uint16_t v) { #if (defined(__MINGW32__) || defined(MINGW32) || defined(BOOST_MINGW32)) constexpr int bn = 4 * sizeof(std::uint16_t); char str[bn]; int n = std::snprintf(str, bn, "%d", v); BOOST_ASSERT(n <= bn); boost::ignore_unused(n); return std::string(str); #else return std::to_string(v); #endif } std::uint16_t default_port(const boost::urls::url_view& u) { if (!u.has_port()) { if (u.scheme_id() == boost::urls::scheme::http) return 80; if (u.scheme_id() == boost::urls::scheme::https) return 445; if (u.scheme().starts_with("socks")) return 1080; } return u.port_number(); } tcp::endpoint get_endpoint_unchecked(const boost::urls::url_view& u) { if (u.host_type() == urls::host_type::ipv4) { urls::ipv4_address ip = urls::parse_ipv4_address(u.encoded_host()).value(); return tcp::endpoint{ ip::address_v4{ip.to_uint()}, default_port(u)}; } urls::ipv6_address ip = urls::parse_ipv6_address(u.encoded_host()).value(); return tcp::endpoint{ ip::address_v6{ip.to_bytes()}, default_port(u)}; } class socks_client { public: explicit socks_client( asio::io_context& ioc, int http_version, string_view target_str, string_view socks_str) : resolver_(ioc) , socket_(ioc) , http_version_(http_version) { parse_urls(target_str, socks_str); if (ec_.failed()) return; do_proxy_resolve(); } bool has_error() const { return static_cast<bool>(ec_); } error_code error() const { return ec_; } private: void do_proxy_resolve() { // Resolve to the SOCKS server if (socks_.host_type() == urls::host_type::name) { // Look up the SOCKS domain name resolver_.async_resolve( std::string(socks_.encoded_host()), std::to_string(default_port(socks_)), [this]( error_code ec, tcp::resolver::results_type endpoints) { if (ec.failed()) return fail(ec, "proxy resolve"); do_proxy_connect( endpoints.begin(), endpoints.end()); }); } else if (socks_.host_type() == urls::host_type::ipv4 || socks_.host_type() == urls::host_type::ipv6) { // Nothing to resolve tcp::endpoint ep = get_endpoint_unchecked(socks_); do_proxy_connect(&ep, (&ep)+1); } else { // Cannot interpret the host fail(asio::error::operation_not_supported, "Unsupported host", socks_.encoded_host()); } } template <class EndpointIt> void do_proxy_connect(EndpointIt first, EndpointIt last) { asio::async_connect(socket_, first, last, [this](error_code ec, EndpointIt) { if (ec.failed()) return fail(ec, "connect"); do_socks_connect(); }); } void do_socks_connect() { // The callback function for whatever // connect function we use auto cb = [this](error_code ec, tcp::endpoint) { if (ec.failed()) return fail(ec, "socks_connect"); do_socks_request(); }; // Send a SOCKS connect request according to the URL if (socks_version_ == 0x05) { if (!socks_.has_userinfo()) { if (target_.host_type() == urls::host_type::name) { socks::async_connect( socket_, target_.encoded_host(), default_port(target_), socks::auth_options::none{}, cb); } else if (target_.host_type() == urls::host_type::ipv4 || target_.host_type() == urls::host_type::ipv6) { socks::async_connect( socket_, get_endpoint_unchecked(target_), socks::auth_options::none{}, cb); } } else { socks::auth_options::userpass a{ socks_.encoded_user(), socks_.encoded_password(), }; if (target_.host_type() == urls::host_type::name) { socks::async_connect( socket_, target_.encoded_host(), default_port(target_), a, cb); } else if (target_.host_type() == urls::host_type::ipv4 || target_.host_type() == urls::host_type::ipv6) { socks::async_connect( socket_, get_endpoint_unchecked(target_), a, cb); } } } else { if (target_.host_type() == urls::host_type::name) { // SOCKS4 does not support domain names. // The domain name needs to be resolved // on the client. std::size_t port = default_port(target_); std::string port_str = to_string(port); using resolve_results = asio::ip::tcp::resolver::results_type; resolver_.async_resolve( std::string(target_.encoded_host()), port_str, [this, cb] (error_code ec, resolve_results eps) { if (ec.failed()) return fail(ec, "resolve"); auto it = eps.begin(); while (it != eps.end()) { auto e = it->endpoint(); if (e.address().is_v4()) { // Send the CONNECT request socks::async_connect_v4( socket_, e, socks_.encoded_user(), cb); return; } ++it; } fail(asio::error::host_not_found, "not ipv4 address found for host"); }); } else if (target_.host_type() == urls::host_type::ipv4 || target_.host_type() == urls::host_type::ipv6) { socks::async_connect_v4( socket_, get_endpoint_unchecked(target_), socks_.encoded_user(), cb); } } } void do_socks_request() { /* * After this point, we can talk to the * SOCKS server as if we were talking to * the application server. */ // Set up an HTTP GET request req_ = {http::verb::get, target_.encoded_host(), http_version_}; req_.set(http::field::host, target_.encoded_host()); req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); std::cout << req_ << std::endl; // Send the HTTP request http::async_write(socket_, req_, [this](error_code ec, std::size_t) { if (ec.failed()) return fail(ec, "write"); do_socks_response(); }); } void do_socks_response() { // Read the HTTP response http::async_read(socket_, buffer_, res_, [this](error_code ec, std::size_t) { if (ec.failed() && ec != beast::http::error::end_of_stream) return fail(ec, "read"); // Print response std::cout << res_ << std::endl; // Close the socket socket_.shutdown( tcp::socket::shutdown_both, ec); if (ec.failed() && ec != beast::errc::not_connected) return fail(ec, "shutdown"); }); } void parse_urls( string_view target_str, string_view socks_str) { // Parse target application URI auto r = urls::parse_uri(target_str); if (!r.has_value()) return fail(r.error(), "Parse target"); target_ = r.value(); // Parse SOCKS server URI r = urls::parse_uri(socks_str); if (!r.has_value()) return fail(r.error(), "Parse SOCKS"); socks_ = r.value(); if (socks_.scheme() == "socks5") socks_version_ = 0x05; else if ( socks_.scheme() == "socks4" || socks_.scheme() == "socks4a") socks_version_ = 0x04; else return fail(asio::error::no_protocol_option, "Invalid SOCKS scheme: ", socks_.scheme()); // Validate parameters if (socks_version_ == 0x04 && target_.host_type() == urls::host_type::ipv6) return fail(asio::error::no_protocol_option, "SOCKS4 does not support IPv6 addresses"); } // Report a failure void fail(error_code ec, char const* what) { std::cerr << what << ": " << ec.category().name() << " - " << ec.message() << "\n"; ec_ = ec; } void fail(error_code ec, char const* what, string_view value) { std::cerr << what << " - " << value << ": " << ec.category().name() << " - " << ec.message() << "\n"; ec_ = ec; } tcp::resolver resolver_; tcp::socket socket_; int http_version_; urls::url target_; urls::url socks_; unsigned char socks_version_{0x04}; beast::flat_buffer buffer_; http::request<http::string_body> req_; http::response<http::string_body> res_; error_code ec_; }; int main(int argc, char** argv) { // Check command line arguments. if (argc != 3) { std::cerr << "Usage: socks_client_async <target URL> <socks URL>\n\n" "Arguments:\n" " - <target URL>: Application server URI\n" " - <socks URL>: SOCKS server URI (<socks[4|5]://[[user]:password@]server:port>)\n\n" "Example:\n" " socks_client_async http://www.example.com:80/ socks5://socks5server.com:1080\n"; return EXIT_FAILURE; } /* * Some SOCKS proxy lists: * https://www.proxy-list.download/SOCKS4 * https://www.proxy-list.download/SOCKS5 * https://spys.one/en/socks-proxy-list/ */ asio::io_context ioc; socks_client c(ioc, 11, argv[1], argv[2]); ioc.run(); if (c.has_error()) { std::cerr << "Client on " << argv[2] << " - " << c.error().category().name() << " - " << c.error().message() << '\n'; return EXIT_FAILURE; } return EXIT_SUCCESS; }