boost::asio 是否原生支持通过 socks5 进行代理连接?
Is there a native support for proxy connection via socks5 for boost::asio?
我正在使用 boost::asio 来处理我的程序和远程服务器之间的网络通信。要与服务器建立连接,我执行以下操作顺序:
namespace ba = boost::asio;
boost::shared_ptr<ba::ssl::context> ssl_ctx;
boost::shared_ptr<boost::asio::io_context> ios; // initialized
boost::shared_ptr<ba::ssl::stream<tcp::socket>> ssl_socket;
ssl_ctx.reset(new ba::ssl::context(boost::asio::ssl::context_base::method::sslv23));
ssl_ctx->set_verify_mode(ba::ssl::verify_peer);
ssl_ctx->add_certificate_authority(ba::buffer(certs.data(), certs.size()));
ssl_socket.reset(new ba::ssl::stream<tcp::socket>(*ios, *ssl_ctx));
auto& socket = ssl_socket->next_layer();
ba::ip::tcp::resolver::iterator ep_iter; // target server
std::future<tcp::resolver::iterator> conn_result = boost::asio::async_connect(socket, ep_iter, boost::asio::use_future);
auto status = conn_result.wait_for(std::chrono::seconds(wait_connection_timeout_sec));
if (status == std::future_status::timeout) {
socket.cancel();
throw std::runtime_error("wait on server connection is timed out");
}
conn_result.get(); // if the operation failed, then conn_result.get() will throw an error
if (!socket.is_open()) {
throw std::runtime_error("can't open socket");
}
socket.set_option(tcp::no_delay(true));
ssl_socket->handshake(ba::ssl::stream<tcp::socket>::handshake_type::client);
此代码完美无缺。连接后,我使用标准方法 write/read 和 ssl_socket。
现在我需要使用 socks5 协议通过代理服务器与我的服务器建立连接。
在boost手册中没有找到解决方案。是否有原生的 socks5 代理支持?我应该在我的代码序列中添加什么,以便 ssl_socket 通过代理服务器连接到我的服务器?
我继续进行手指练习并实施 socks5.hpp
就像我之前对 socks4 所做的那样:socks4 with asynchronous boost::asio
事不宜迟:
tcp::resolver::query target("example.com", "443");
std::future<void> conn_result = socks5::async_proxy_connect(
socket, target, tcp::endpoint{{}, 1080}, ba::use_future);
或者,当然,只是同步:
socks5::proxy_connect(socket, target, tcp::endpoint{{}, 1080});
现场演示
文件socks5.hpp
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
namespace socks5 { // threw in the kitchen sink for error codes
#ifdef STANDALONE_ASIO
using std::error_category;
using std::error_code;
using std::error_condition;
using std::system_error;
#else
namespace asio = boost::asio;
using boost::system::error_category;
using boost::system::error_code;
using boost::system::error_condition;
using boost::system::system_error;
#endif
enum class result_code {
ok = 0,
invalid_version = 1,
disallowed = 2,
auth_method_rejected = 3,
network_unreachable = 4,
host_unreachable = 5,
connection_refused = 6,
ttl_expired = 7,
command_not_supported = 8,
address_type_not_supported = 9,
//
failed = 99,
};
auto const& get_result_category() {
struct impl : error_category {
const char* name() const noexcept override { return "result_code"; }
std::string message(int ev) const override {
switch (static_cast<result_code>(ev)) {
case result_code::ok: return "Success";
case result_code::invalid_version: return "SOCKS5 invalid reply version";
case result_code::disallowed: return "SOCKS5 disallowed";
case result_code::auth_method_rejected: return "SOCKS5 no accepted authentication method";
case result_code::network_unreachable: return "SOCKS5 network unreachable";
case result_code::host_unreachable: return "SOCKS5 host unreachable";
case result_code::connection_refused: return "SOCKS5 connection refused";
case result_code::ttl_expired: return "SOCKS5 TTL expired";
case result_code::command_not_supported: return "SOCKS5 command not supported";
case result_code::address_type_not_supported: return "SOCKS5 address type not supported";
case result_code::failed: return "SOCKS5 general unexpected failure";
default: return "unknown error";
}
}
error_condition
default_error_condition(int ev) const noexcept override {
return error_condition{ev, *this};
}
bool equivalent(int ev, error_condition const& condition)
const noexcept override {
return condition.value() == ev && &condition.category() == this;
}
bool equivalent(error_code const& error,
int ev) const noexcept override {
return error.value() == ev && &error.category() == this;
}
} const static instance;
return instance;
}
error_code make_error_code(result_code se) {
return error_code{
static_cast<std::underlying_type<result_code>::type>(se),
get_result_category()};
}
} // namespace socks5
template <>
struct boost::system::is_error_code_enum<socks5::result_code>
: std::true_type {};
namespace socks5 {
using namespace std::placeholders;
template <typename Proto> struct core_t {
using Endpoint = typename Proto::endpoint;
using Query = typename boost::asio::ip::basic_resolver<Proto>::query;
Endpoint _proxy;
core_t(Query const& target, Endpoint proxy)
: _proxy(proxy)
, _request(target)
{
}
core_t(Endpoint const& target, Endpoint proxy)
: _proxy(proxy)
, _request(target)
{
}
#pragma pack(push)
#pragma pack(1)
enum class addr_type : uint8_t { IPv4 = 0x01, Domain = 0x03, IPv6 = 0x04 };
enum class auth_method : uint8_t {
none = 0x00, // No authentication
gssapi = 0x01, // GSSAPI (RFC 1961
basic = 0x02, // Username/password (RFC 1929)
// 0x03–0x7F methods assigned by IANA[11]
challenge_handshake = 0x03, // Challenge-Handshake Authentication Protocol
challenge_response = 0x05, // Challenge-Response Authentication Method
ssl = 0x06, // Secure Sockets Layer
nds = 0x07, // NDS Authentication
maf = 0x08, // Multi-Authentication Framework
json = 0x09, // JSON Parameter Block
};
enum class version : uint8_t {
none = 0x00,
socks4 = 0x04,
socks5 = 0x05,
};
enum class proxy_command : uint8_t {
connect = 0x01,
bind = 0x02,
udp_associate = 0x03,
};
enum class proxy_reply : uint8_t {
succeeded = 0x00,
general_failure = 0x01,
disallowed = 0x02,
network_unreachable = 0x03,
host_unreachable = 0x04,
connection_refused = 0x05,
ttl_expired = 0x06,
command_not_supported = 0x07,
address_type_not_supported = 0x08,
};
using ipv4_octets = boost::asio::ip::address_v4::bytes_type;
using ipv6_octets = boost::asio::ip::address_v6::bytes_type;
using net_short = boost::endian::big_uint16_t;
struct {
version ver = version::socks5;
uint8_t nmethods = 0x01;
auth_method method[1] = {auth_method::none};
} _greeting;
struct {
version reply_version;
uint8_t cauth;
} _greeting_response;
struct wire_address {
addr_type type{};
union {
ipv4_octets ipv4;
ipv6_octets ipv6;
std::array<uint8_t, 256> domain{0}; // length prefixed
} payload;
size_t var_length() const {
return sizeof(type) + payload_length();
}
size_t payload_length() const
{
switch (type) {
case addr_type::IPv4: return sizeof(payload.ipv4);
case addr_type::IPv6: return sizeof(payload.ipv6);
case addr_type::Domain:
assert(payload.domain[0] < payload.domain.max_size());
return 1 + payload.domain[0];
}
return 0;
}
};
struct request_t {
version ver = version::socks5;
proxy_command cmd = proxy_command::connect;
uint8_t reserved = 0;
wire_address var_address;
net_short port;
// constructors
request_t(Endpoint const& ep) : port(ep.port())
{
auto& addr = ep.address();
if (addr.is_v4()) {
var_address.type = addr_type::IPv4;
var_address.payload.ipv4 = addr.to_v4().to_bytes();
} else {
var_address.type = addr_type::IPv6;
var_address.payload.ipv6 = addr.to_v6().to_bytes();
}
}
request_t(Query const& q) : port(std::stoi(q.service_name())) {
std::string const domain = q.host_name();
var_address.type = addr_type::Domain;
auto len = std::min(var_address.payload.domain.max_size() - 1,
domain.length());
assert(len == domain.length() || "domain truncated");
var_address.payload.domain[0] = len;
std::copy_n(domain.data(), len,
var_address.payload.domain.data() + 1);
}
auto buffers() const {
return std::array {
boost::asio::buffer(this, offsetof(request_t, var_address)),
boost::asio::buffer(&var_address, var_address.var_length()),
boost::asio::buffer(&port, sizeof(port)),
};
}
} _request;
struct response_t {
version reply_version;
proxy_reply reply;
uint8_t reserved = 0x0;
wire_address var_address {addr_type::IPv4};
net_short port;
auto head_buffers() {
return std::array{
boost::asio::buffer(this, offsetof(response_t, var_address) + sizeof(addr_type)),
};
}
auto tail_buffers() { // depends on head_buffers being correctly received!
return std::array{
boost::asio::buffer(&var_address.payload, var_address.payload_length()),
boost::asio::buffer(&port, sizeof(port)),
};
}
} _response;
#pragma pack(pop)
using const_buffer = boost::asio::const_buffer;
using mutable_buffer = boost::asio::mutable_buffer;
auto greeting_buffers() const {
return boost::asio::buffer(&_greeting, sizeof(_greeting));
}
auto greeting_response_buffers() {
return boost::asio::buffer(&_greeting_response, sizeof(_greeting_response));
}
auto request_buffers() const { return _request.buffers(); }
auto response_head_buffers() { return _response.head_buffers(); }
auto response_tail_buffers() { return _response.tail_buffers(); }
error_code get_greeting_result(error_code ec = {}) const {
if (ec)
return ec;
if (_greeting_response.reply_version != version::socks5)
return result_code::invalid_version;
if (_greeting_response.cauth != 0) {
return result_code::auth_method_rejected;
}
return result_code::ok;
}
error_code get_result(error_code ec = {}) const {
if (ec)
return ec;
if (_response.reply_version != version::socks5)
return result_code::invalid_version;
switch (_response.reply) {
case proxy_reply::succeeded: return result_code::ok;
case proxy_reply::disallowed: return result_code::disallowed;
case proxy_reply::network_unreachable: return result_code::network_unreachable;
case proxy_reply::host_unreachable: return result_code::host_unreachable;
case proxy_reply::connection_refused: return result_code::connection_refused;
case proxy_reply::ttl_expired: return result_code::ttl_expired;
case proxy_reply::command_not_supported: return result_code::command_not_supported;
case proxy_reply::address_type_not_supported: return result_code::address_type_not_supported;
case proxy_reply::general_failure: break;
}
return result_code::failed;
};
};
template <typename Socket, typename Completion>
struct async_proxy_connect_op {
using Proto = typename Socket::protocol_type;
using Endpoint = typename Proto::endpoint;
using executor_type = typename Socket::executor_type;
auto get_executor() { return _socket.get_executor(); }
private:
core_t<Proto> _core;
Socket& _socket;
Completion _handler;
public:
template <typename EndpointOrQuery>
async_proxy_connect_op(Completion handler, Socket& s,
EndpointOrQuery target, Endpoint proxy)
: _core(target, proxy)
, _socket(s)
, _handler(std::move(handler))
{
}
using Self = std::unique_ptr<async_proxy_connect_op>;
void init(Self&& self) { operator()(self, INIT{}); }
private:
// states
struct INIT{};
struct CONNECT{};
struct GREETING_SENT{};
struct ONGREETING_RESPONSE{};
struct REQUEST_SENT{};
struct ON_RESPONSE_HEAD{};
struct ON_RESPONSE_TAIL{};
struct Binder {
Self _self;
template <typename... Args>
decltype(auto) operator()(Args&&... args) {
return (*_self)(_self, std::forward<Args>(args)...);
}
};
void operator()(Self& self, INIT) {
_socket.async_connect(_core._proxy,
std::bind(Binder{std::move(self)}, CONNECT{}, _1));
}
void operator()(Self& self, CONNECT, error_code ec) {
if (ec) return _handler(ec);
boost::asio::async_write(
_socket,
_core.greeting_buffers(),
std::bind(Binder{std::move(self)}, GREETING_SENT{}, _1, _2));
}
void operator()(Self& self, GREETING_SENT, error_code ec, size_t xfer) {
if (ec) return _handler(ec);
auto buf = _core.greeting_response_buffers();
boost::asio::async_read(
_socket, buf, boost::asio::transfer_exactly(buffer_size(buf)),
std::bind(Binder{std::move(self)}, ONGREETING_RESPONSE{}, _1, _2));
}
void operator()(Self& self, ONGREETING_RESPONSE, error_code ec, size_t xfer) {
ec = _core.get_greeting_result(ec);
if (ec) return _handler(ec);
boost::asio::async_write(
_socket, _core.request_buffers(),
std::bind(Binder{std::move(self)}, REQUEST_SENT{}, _1, _2));
}
void operator()(Self& self, REQUEST_SENT, error_code ec, size_t xfer) {
if (ec) return _handler(ec);
auto buf = _core.response_head_buffers();
boost::asio::async_read(
_socket, buf, boost::asio::transfer_exactly(buffer_size(buf)),
std::bind(Binder{std::move(self)}, ON_RESPONSE_HEAD{}, _1, _2));
}
void operator()(Self& self, ON_RESPONSE_HEAD, error_code ec, size_t xfer) {
if (ec) return _handler(ec);
auto buf = _core.response_tail_buffers();
boost::asio::async_read(
_socket, buf, boost::asio::transfer_exactly(buffer_size(buf)),
std::bind(Binder{std::move(self)}, ON_RESPONSE_TAIL{}, _1, _2));
}
void operator()(Self& self, ON_RESPONSE_TAIL, error_code ec, size_t xfer) {
_handler(_core.get_result(ec));
}
};
template <typename Socket, typename EndpointOrQuery,
typename Endpoint = typename Socket::protocol_type::endpoint>
error_code proxy_connect(Socket& s, EndpointOrQuery target, Endpoint proxy,
error_code& ec)
{
core_t<typename Socket::protocol_type> core(target, proxy);
ec.clear();
s.connect(core._proxy, ec);
if (!ec)
boost::asio::write(s, core.greeting_buffers(), ec);
using boost::asio::transfer_exactly;
if (!ec) {
auto buf = core.greeting_response_buffers();
boost::asio::read(s, buf, transfer_exactly(buffer_size(buf)), ec);
}
ec = core.get_greeting_result(ec);
if (!ec) {
boost::asio::write(s, core.request_buffers(), ec);
}
if (!ec) {
auto buf = core.response_head_buffers();
boost::asio::read(s, buf, transfer_exactly(buffer_size(buf)), ec);
}
if (!ec) {
auto buf = core.response_tail_buffers();
boost::asio::read(s, buf, transfer_exactly(buffer_size(buf)), ec);
}
return ec = core.get_result(ec);
}
template <typename Socket, typename EndpointOrQuery,
typename Endpoint = typename Socket::protocol_type::endpoint>
void proxy_connect(Socket& s, EndpointOrQuery target, Endpoint proxy)
{
error_code ec;
if (proxy_connect(s, target, proxy, ec))
throw system_error(ec);
}
template <typename Socket, typename Token, typename EndpointOrQuery,
typename Endpoint = typename Socket::protocol_type::endpoint>
auto async_proxy_connect(Socket& s, EndpointOrQuery target, Endpoint proxy,
Token&& token)
{
using Result = asio::async_result<std::decay_t<Token>, void(error_code)>;
using Completion = typename Result::completion_handler_type;
Completion completion(std::forward<Token>(token));
Result result(completion);
using Op = async_proxy_connect_op<Socket, Completion>;
// make an owning self ptr, to serve a unique async chain
auto self = std::make_unique<Op>(completion, s, target, proxy);
self->init(std::move(self));
return result.get();
}
} // namespace socks5
文件test.cpp
#include "socks5.hpp"
///////////
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <boost/make_shared.hpp>
#include <iostream>
namespace ba = boost::asio;
namespace http = boost::beast::http;
namespace ssl = boost::asio::ssl;
using namespace std::chrono_literals;
using ba::ip::tcp;
static constexpr auto connection_timeout = 1s;
int main()
{
auto ios = boost::make_shared<ba::io_context>();
auto ssl_ctx =
boost::make_shared<ssl::context>(ssl::context_base::method::sslv23);
ssl_ctx->set_verify_mode(ssl::verify_peer);
//ssl_ctx->add_certificate_authority(ba::buffer(certs.data(), certs.size()));
ssl_ctx->set_default_verify_paths(); // FOR DEMO
auto ssl_socket =
boost::make_shared<ssl::stream<tcp::socket>>(*ios, *ssl_ctx);
auto& socket = ssl_socket->next_layer();
tcp::resolver::query target("example.com", "443");
#if 1
std::future<void> conn_result = socks5::async_proxy_connect(
socket, target, tcp::endpoint{{}, 1080}, ba::use_future);
std::thread th([ios] { ios->run(); });
if (conn_result.wait_for(connection_timeout) ==
std::future_status::timeout) {
socket.cancel();
// no need to throw, `conn_result.get()` will give operation_aborted
}
conn_result.get(); // may throw error
#else // synchronously as well:
socks5::proxy_connect(socket, target, tcp::endpoint{{}, 1080});
#endif
socket.set_option(tcp::no_delay(true));
ssl_socket->handshake(ssl::stream_base::handshake_type::client);
{
http::request<http::empty_body> req(http::verb::get, "/", 11);
req.set(http::field::host, "example.com");
req.prepare_payload();
http::write(*ssl_socket, req);
}
{
http::response<http::string_body> res;
boost::beast::flat_buffer buf;
http::read(*ssl_socket, buf, res);
std::cout << res;
}
th.join();
}
我的系统使用 ssh
SOCKS 服务器测试正确:
我正在使用 boost::asio 来处理我的程序和远程服务器之间的网络通信。要与服务器建立连接,我执行以下操作顺序:
namespace ba = boost::asio;
boost::shared_ptr<ba::ssl::context> ssl_ctx;
boost::shared_ptr<boost::asio::io_context> ios; // initialized
boost::shared_ptr<ba::ssl::stream<tcp::socket>> ssl_socket;
ssl_ctx.reset(new ba::ssl::context(boost::asio::ssl::context_base::method::sslv23));
ssl_ctx->set_verify_mode(ba::ssl::verify_peer);
ssl_ctx->add_certificate_authority(ba::buffer(certs.data(), certs.size()));
ssl_socket.reset(new ba::ssl::stream<tcp::socket>(*ios, *ssl_ctx));
auto& socket = ssl_socket->next_layer();
ba::ip::tcp::resolver::iterator ep_iter; // target server
std::future<tcp::resolver::iterator> conn_result = boost::asio::async_connect(socket, ep_iter, boost::asio::use_future);
auto status = conn_result.wait_for(std::chrono::seconds(wait_connection_timeout_sec));
if (status == std::future_status::timeout) {
socket.cancel();
throw std::runtime_error("wait on server connection is timed out");
}
conn_result.get(); // if the operation failed, then conn_result.get() will throw an error
if (!socket.is_open()) {
throw std::runtime_error("can't open socket");
}
socket.set_option(tcp::no_delay(true));
ssl_socket->handshake(ba::ssl::stream<tcp::socket>::handshake_type::client);
此代码完美无缺。连接后,我使用标准方法 write/read 和 ssl_socket。
现在我需要使用 socks5 协议通过代理服务器与我的服务器建立连接。 在boost手册中没有找到解决方案。是否有原生的 socks5 代理支持?我应该在我的代码序列中添加什么,以便 ssl_socket 通过代理服务器连接到我的服务器?
我继续进行手指练习并实施 socks5.hpp
就像我之前对 socks4 所做的那样:socks4 with asynchronous boost::asio
事不宜迟:
tcp::resolver::query target("example.com", "443");
std::future<void> conn_result = socks5::async_proxy_connect(
socket, target, tcp::endpoint{{}, 1080}, ba::use_future);
或者,当然,只是同步:
socks5::proxy_connect(socket, target, tcp::endpoint{{}, 1080});
现场演示
文件
socks5.hpp
#include <boost/asio.hpp> #include <boost/endian/arithmetic.hpp> namespace socks5 { // threw in the kitchen sink for error codes #ifdef STANDALONE_ASIO using std::error_category; using std::error_code; using std::error_condition; using std::system_error; #else namespace asio = boost::asio; using boost::system::error_category; using boost::system::error_code; using boost::system::error_condition; using boost::system::system_error; #endif enum class result_code { ok = 0, invalid_version = 1, disallowed = 2, auth_method_rejected = 3, network_unreachable = 4, host_unreachable = 5, connection_refused = 6, ttl_expired = 7, command_not_supported = 8, address_type_not_supported = 9, // failed = 99, }; auto const& get_result_category() { struct impl : error_category { const char* name() const noexcept override { return "result_code"; } std::string message(int ev) const override { switch (static_cast<result_code>(ev)) { case result_code::ok: return "Success"; case result_code::invalid_version: return "SOCKS5 invalid reply version"; case result_code::disallowed: return "SOCKS5 disallowed"; case result_code::auth_method_rejected: return "SOCKS5 no accepted authentication method"; case result_code::network_unreachable: return "SOCKS5 network unreachable"; case result_code::host_unreachable: return "SOCKS5 host unreachable"; case result_code::connection_refused: return "SOCKS5 connection refused"; case result_code::ttl_expired: return "SOCKS5 TTL expired"; case result_code::command_not_supported: return "SOCKS5 command not supported"; case result_code::address_type_not_supported: return "SOCKS5 address type not supported"; case result_code::failed: return "SOCKS5 general unexpected failure"; default: return "unknown error"; } } error_condition default_error_condition(int ev) const noexcept override { return error_condition{ev, *this}; } bool equivalent(int ev, error_condition const& condition) const noexcept override { return condition.value() == ev && &condition.category() == this; } bool equivalent(error_code const& error, int ev) const noexcept override { return error.value() == ev && &error.category() == this; } } const static instance; return instance; } error_code make_error_code(result_code se) { return error_code{ static_cast<std::underlying_type<result_code>::type>(se), get_result_category()}; } } // namespace socks5 template <> struct boost::system::is_error_code_enum<socks5::result_code> : std::true_type {}; namespace socks5 { using namespace std::placeholders; template <typename Proto> struct core_t { using Endpoint = typename Proto::endpoint; using Query = typename boost::asio::ip::basic_resolver<Proto>::query; Endpoint _proxy; core_t(Query const& target, Endpoint proxy) : _proxy(proxy) , _request(target) { } core_t(Endpoint const& target, Endpoint proxy) : _proxy(proxy) , _request(target) { } #pragma pack(push) #pragma pack(1) enum class addr_type : uint8_t { IPv4 = 0x01, Domain = 0x03, IPv6 = 0x04 }; enum class auth_method : uint8_t { none = 0x00, // No authentication gssapi = 0x01, // GSSAPI (RFC 1961 basic = 0x02, // Username/password (RFC 1929) // 0x03–0x7F methods assigned by IANA[11] challenge_handshake = 0x03, // Challenge-Handshake Authentication Protocol challenge_response = 0x05, // Challenge-Response Authentication Method ssl = 0x06, // Secure Sockets Layer nds = 0x07, // NDS Authentication maf = 0x08, // Multi-Authentication Framework json = 0x09, // JSON Parameter Block }; enum class version : uint8_t { none = 0x00, socks4 = 0x04, socks5 = 0x05, }; enum class proxy_command : uint8_t { connect = 0x01, bind = 0x02, udp_associate = 0x03, }; enum class proxy_reply : uint8_t { succeeded = 0x00, general_failure = 0x01, disallowed = 0x02, network_unreachable = 0x03, host_unreachable = 0x04, connection_refused = 0x05, ttl_expired = 0x06, command_not_supported = 0x07, address_type_not_supported = 0x08, }; using ipv4_octets = boost::asio::ip::address_v4::bytes_type; using ipv6_octets = boost::asio::ip::address_v6::bytes_type; using net_short = boost::endian::big_uint16_t; struct { version ver = version::socks5; uint8_t nmethods = 0x01; auth_method method[1] = {auth_method::none}; } _greeting; struct { version reply_version; uint8_t cauth; } _greeting_response; struct wire_address { addr_type type{}; union { ipv4_octets ipv4; ipv6_octets ipv6; std::array<uint8_t, 256> domain{0}; // length prefixed } payload; size_t var_length() const { return sizeof(type) + payload_length(); } size_t payload_length() const { switch (type) { case addr_type::IPv4: return sizeof(payload.ipv4); case addr_type::IPv6: return sizeof(payload.ipv6); case addr_type::Domain: assert(payload.domain[0] < payload.domain.max_size()); return 1 + payload.domain[0]; } return 0; } }; struct request_t { version ver = version::socks5; proxy_command cmd = proxy_command::connect; uint8_t reserved = 0; wire_address var_address; net_short port; // constructors request_t(Endpoint const& ep) : port(ep.port()) { auto& addr = ep.address(); if (addr.is_v4()) { var_address.type = addr_type::IPv4; var_address.payload.ipv4 = addr.to_v4().to_bytes(); } else { var_address.type = addr_type::IPv6; var_address.payload.ipv6 = addr.to_v6().to_bytes(); } } request_t(Query const& q) : port(std::stoi(q.service_name())) { std::string const domain = q.host_name(); var_address.type = addr_type::Domain; auto len = std::min(var_address.payload.domain.max_size() - 1, domain.length()); assert(len == domain.length() || "domain truncated"); var_address.payload.domain[0] = len; std::copy_n(domain.data(), len, var_address.payload.domain.data() + 1); } auto buffers() const { return std::array { boost::asio::buffer(this, offsetof(request_t, var_address)), boost::asio::buffer(&var_address, var_address.var_length()), boost::asio::buffer(&port, sizeof(port)), }; } } _request; struct response_t { version reply_version; proxy_reply reply; uint8_t reserved = 0x0; wire_address var_address {addr_type::IPv4}; net_short port; auto head_buffers() { return std::array{ boost::asio::buffer(this, offsetof(response_t, var_address) + sizeof(addr_type)), }; } auto tail_buffers() { // depends on head_buffers being correctly received! return std::array{ boost::asio::buffer(&var_address.payload, var_address.payload_length()), boost::asio::buffer(&port, sizeof(port)), }; } } _response; #pragma pack(pop) using const_buffer = boost::asio::const_buffer; using mutable_buffer = boost::asio::mutable_buffer; auto greeting_buffers() const { return boost::asio::buffer(&_greeting, sizeof(_greeting)); } auto greeting_response_buffers() { return boost::asio::buffer(&_greeting_response, sizeof(_greeting_response)); } auto request_buffers() const { return _request.buffers(); } auto response_head_buffers() { return _response.head_buffers(); } auto response_tail_buffers() { return _response.tail_buffers(); } error_code get_greeting_result(error_code ec = {}) const { if (ec) return ec; if (_greeting_response.reply_version != version::socks5) return result_code::invalid_version; if (_greeting_response.cauth != 0) { return result_code::auth_method_rejected; } return result_code::ok; } error_code get_result(error_code ec = {}) const { if (ec) return ec; if (_response.reply_version != version::socks5) return result_code::invalid_version; switch (_response.reply) { case proxy_reply::succeeded: return result_code::ok; case proxy_reply::disallowed: return result_code::disallowed; case proxy_reply::network_unreachable: return result_code::network_unreachable; case proxy_reply::host_unreachable: return result_code::host_unreachable; case proxy_reply::connection_refused: return result_code::connection_refused; case proxy_reply::ttl_expired: return result_code::ttl_expired; case proxy_reply::command_not_supported: return result_code::command_not_supported; case proxy_reply::address_type_not_supported: return result_code::address_type_not_supported; case proxy_reply::general_failure: break; } return result_code::failed; }; }; template <typename Socket, typename Completion> struct async_proxy_connect_op { using Proto = typename Socket::protocol_type; using Endpoint = typename Proto::endpoint; using executor_type = typename Socket::executor_type; auto get_executor() { return _socket.get_executor(); } private: core_t<Proto> _core; Socket& _socket; Completion _handler; public: template <typename EndpointOrQuery> async_proxy_connect_op(Completion handler, Socket& s, EndpointOrQuery target, Endpoint proxy) : _core(target, proxy) , _socket(s) , _handler(std::move(handler)) { } using Self = std::unique_ptr<async_proxy_connect_op>; void init(Self&& self) { operator()(self, INIT{}); } private: // states struct INIT{}; struct CONNECT{}; struct GREETING_SENT{}; struct ONGREETING_RESPONSE{}; struct REQUEST_SENT{}; struct ON_RESPONSE_HEAD{}; struct ON_RESPONSE_TAIL{}; struct Binder { Self _self; template <typename... Args> decltype(auto) operator()(Args&&... args) { return (*_self)(_self, std::forward<Args>(args)...); } }; void operator()(Self& self, INIT) { _socket.async_connect(_core._proxy, std::bind(Binder{std::move(self)}, CONNECT{}, _1)); } void operator()(Self& self, CONNECT, error_code ec) { if (ec) return _handler(ec); boost::asio::async_write( _socket, _core.greeting_buffers(), std::bind(Binder{std::move(self)}, GREETING_SENT{}, _1, _2)); } void operator()(Self& self, GREETING_SENT, error_code ec, size_t xfer) { if (ec) return _handler(ec); auto buf = _core.greeting_response_buffers(); boost::asio::async_read( _socket, buf, boost::asio::transfer_exactly(buffer_size(buf)), std::bind(Binder{std::move(self)}, ONGREETING_RESPONSE{}, _1, _2)); } void operator()(Self& self, ONGREETING_RESPONSE, error_code ec, size_t xfer) { ec = _core.get_greeting_result(ec); if (ec) return _handler(ec); boost::asio::async_write( _socket, _core.request_buffers(), std::bind(Binder{std::move(self)}, REQUEST_SENT{}, _1, _2)); } void operator()(Self& self, REQUEST_SENT, error_code ec, size_t xfer) { if (ec) return _handler(ec); auto buf = _core.response_head_buffers(); boost::asio::async_read( _socket, buf, boost::asio::transfer_exactly(buffer_size(buf)), std::bind(Binder{std::move(self)}, ON_RESPONSE_HEAD{}, _1, _2)); } void operator()(Self& self, ON_RESPONSE_HEAD, error_code ec, size_t xfer) { if (ec) return _handler(ec); auto buf = _core.response_tail_buffers(); boost::asio::async_read( _socket, buf, boost::asio::transfer_exactly(buffer_size(buf)), std::bind(Binder{std::move(self)}, ON_RESPONSE_TAIL{}, _1, _2)); } void operator()(Self& self, ON_RESPONSE_TAIL, error_code ec, size_t xfer) { _handler(_core.get_result(ec)); } }; template <typename Socket, typename EndpointOrQuery, typename Endpoint = typename Socket::protocol_type::endpoint> error_code proxy_connect(Socket& s, EndpointOrQuery target, Endpoint proxy, error_code& ec) { core_t<typename Socket::protocol_type> core(target, proxy); ec.clear(); s.connect(core._proxy, ec); if (!ec) boost::asio::write(s, core.greeting_buffers(), ec); using boost::asio::transfer_exactly; if (!ec) { auto buf = core.greeting_response_buffers(); boost::asio::read(s, buf, transfer_exactly(buffer_size(buf)), ec); } ec = core.get_greeting_result(ec); if (!ec) { boost::asio::write(s, core.request_buffers(), ec); } if (!ec) { auto buf = core.response_head_buffers(); boost::asio::read(s, buf, transfer_exactly(buffer_size(buf)), ec); } if (!ec) { auto buf = core.response_tail_buffers(); boost::asio::read(s, buf, transfer_exactly(buffer_size(buf)), ec); } return ec = core.get_result(ec); } template <typename Socket, typename EndpointOrQuery, typename Endpoint = typename Socket::protocol_type::endpoint> void proxy_connect(Socket& s, EndpointOrQuery target, Endpoint proxy) { error_code ec; if (proxy_connect(s, target, proxy, ec)) throw system_error(ec); } template <typename Socket, typename Token, typename EndpointOrQuery, typename Endpoint = typename Socket::protocol_type::endpoint> auto async_proxy_connect(Socket& s, EndpointOrQuery target, Endpoint proxy, Token&& token) { using Result = asio::async_result<std::decay_t<Token>, void(error_code)>; using Completion = typename Result::completion_handler_type; Completion completion(std::forward<Token>(token)); Result result(completion); using Op = async_proxy_connect_op<Socket, Completion>; // make an owning self ptr, to serve a unique async chain auto self = std::make_unique<Op>(completion, s, target, proxy); self->init(std::move(self)); return result.get(); } } // namespace socks5
文件
test.cpp
#include "socks5.hpp" /////////// #include <boost/asio/ssl.hpp> #include <boost/beast.hpp> #include <boost/beast/http.hpp> #include <boost/make_shared.hpp> #include <iostream> namespace ba = boost::asio; namespace http = boost::beast::http; namespace ssl = boost::asio::ssl; using namespace std::chrono_literals; using ba::ip::tcp; static constexpr auto connection_timeout = 1s; int main() { auto ios = boost::make_shared<ba::io_context>(); auto ssl_ctx = boost::make_shared<ssl::context>(ssl::context_base::method::sslv23); ssl_ctx->set_verify_mode(ssl::verify_peer); //ssl_ctx->add_certificate_authority(ba::buffer(certs.data(), certs.size())); ssl_ctx->set_default_verify_paths(); // FOR DEMO auto ssl_socket = boost::make_shared<ssl::stream<tcp::socket>>(*ios, *ssl_ctx); auto& socket = ssl_socket->next_layer(); tcp::resolver::query target("example.com", "443"); #if 1 std::future<void> conn_result = socks5::async_proxy_connect( socket, target, tcp::endpoint{{}, 1080}, ba::use_future); std::thread th([ios] { ios->run(); }); if (conn_result.wait_for(connection_timeout) == std::future_status::timeout) { socket.cancel(); // no need to throw, `conn_result.get()` will give operation_aborted } conn_result.get(); // may throw error #else // synchronously as well: socks5::proxy_connect(socket, target, tcp::endpoint{{}, 1080}); #endif socket.set_option(tcp::no_delay(true)); ssl_socket->handshake(ssl::stream_base::handshake_type::client); { http::request<http::empty_body> req(http::verb::get, "/", 11); req.set(http::field::host, "example.com"); req.prepare_payload(); http::write(*ssl_socket, req); } { http::response<http::string_body> res; boost::beast::flat_buffer buf; http::read(*ssl_socket, buf, res); std::cout << res; } th.join(); }
我的系统使用 ssh
SOCKS 服务器测试正确: