aboutsummaryrefslogtreecommitdiff
path: root/src/net/socks.cpp
diff options
context:
space:
mode:
authortobtoht <tob@featherwallet.org>2026-04-20 07:49:04 +0000
committertobtoht <tob@featherwallet.org>2026-04-20 07:49:04 +0000
commit8b6be66bef8cce6e074074b7329be9509b93dbb8 (patch)
tree6dee62eec590447f49e6a29d649c294f4757a2a4 /src/net/socks.cpp
parente75125a30b1f70e74eded99d3fc4f3e98938c6d5 (diff)
parentce5437f0677966c15d2946bc7c068975aa6bfd83 (diff)
downloadmonzero-core-8b6be66bef8cce6e074074b7329be9509b93dbb8.tar.gz
monzero-core-8b6be66bef8cce6e074074b7329be9509b93dbb8.tar.xz
monzero-core-8b6be66bef8cce6e074074b7329be9509b93dbb8.zip
Merge pull request #10411
ce5437f Add Socks v5 support to daemon and wallet (Lee *!* Clagett)
Diffstat (limited to 'src/net/socks.cpp')
-rw-r--r--src/net/socks.cpp515
1 files changed, 489 insertions, 26 deletions
diff --git a/src/net/socks.cpp b/src/net/socks.cpp
index 43d98024b..fc5be424e 100644
--- a/src/net/socks.cpp
+++ b/src/net/socks.cpp
@@ -31,6 +31,7 @@
#include <algorithm>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/buffer.hpp>
+#include <boost/asio/coroutine.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
@@ -38,8 +39,10 @@
#include <boost/endian/conversion.hpp>
#include <cstring>
#include <limits>
+#include <numeric>
#include <string>
+#include "net/parse.h"
#include "net/net_utils_base.h"
#include "net/tor_address.h"
#include "net/i2p_address.h"
@@ -54,6 +57,16 @@ namespace socks
constexpr const std::uint8_t v4tor_resolve_command = 0xf0;
constexpr const std::uint8_t v4_request_granted = 90;
+ constexpr const std::uint8_t v5_noauth_method = 0;
+ constexpr const std::uint8_t v5_userpass_method = 2;
+ constexpr const std::uint8_t v5_connect_command = 1;
+ constexpr const std::uint8_t v5_reserved = 0;
+ constexpr const std::uint8_t v5_ipv4_type = 1;
+ constexpr const std::uint8_t v5_domain_type = 3;
+ constexpr const std::uint8_t v5_ipv6_type = 4;
+ constexpr const std::uint8_t v5_reply_success = 0;
+ constexpr const std::uint8_t v5_userpass_version = 1;
+
struct v4_header
{
std::uint8_t version;
@@ -62,6 +75,114 @@ namespace socks
boost::endian::big_uint32_t ip;
};
+ struct v5_noauth_initial
+ {
+ std::uint8_t version;
+ std::uint8_t n_methods;
+ std::uint8_t method;
+
+ static constexpr v5_noauth_initial make() noexcept
+ {
+ return {5, 1, v5_noauth_method};
+ }
+ };
+
+ struct v5_auth_initial
+ {
+ std::uint8_t version;
+ std::uint8_t n_methods;
+ std::uint8_t method1;
+ std::uint8_t method2;
+
+ static constexpr v5_auth_initial make() noexcept
+ {
+ return {5, 2, v5_noauth_method, v5_userpass_method};
+ }
+ };
+
+ struct v5_response_initial
+ {
+ std::uint8_t version;
+ std::uint8_t method;
+ };
+
+ struct v5_ipv4_connect
+ {
+ std::uint8_t version;
+ std::uint8_t command;
+ std::uint8_t reserved;
+ std::uint8_t type;
+ boost::endian::big_uint32_t ip;
+ boost::endian::big_uint16_t port;
+
+ static v5_ipv4_connect make(const std::uint32_t ip, const std::uint16_t port) noexcept
+ {
+ return {5, v5_connect_command, v5_reserved, v5_ipv4_type, ip, port};
+ }
+ };
+
+ struct v5_domain_connect
+ {
+ std::uint8_t version;
+ std::uint8_t command;
+ std::uint8_t reserved;
+ std::uint8_t type;
+ std::uint8_t length;
+
+ static constexpr v5_domain_connect make(const std::uint8_t length) noexcept
+ {
+ return {5, v5_connect_command, v5_reserved, v5_domain_type, length};
+ }
+ };
+
+ struct v5_ipv6_connect
+ {
+ std::uint8_t version;
+ std::uint8_t command;
+ std::uint8_t reserved;
+ std::uint8_t type;
+ char ip[16];
+ boost::endian::big_uint16_t port;
+
+ static v5_ipv6_connect make(const boost::asio::ip::address_v6& ip, const std::uint16_t port)
+ {
+ v5_ipv6_connect out{5, v5_connect_command, v5_reserved, v5_ipv6_type};
+ out.port = port;
+
+ const auto ip_bytes = ip.to_bytes();
+ static_assert(sizeof(out.ip) == sizeof(ip_bytes), "unexpected ipv6 bytes size");
+ std::memcpy(std::addressof(out.ip), std::addressof(ip_bytes), sizeof(out.ip));
+
+ return out;
+ }
+ };
+
+ struct v5_response_auth
+ {
+ std::uint8_t version;
+ std::uint8_t status;
+ };
+
+ struct v5_response_connect
+ {
+ std::uint8_t version;
+ std::uint8_t reply;
+ std::uint8_t reserved;
+ std::uint8_t type;
+ };
+
+ struct v5_response_ipv4
+ {
+ boost::endian::big_uint32_t ip;
+ boost::endian::big_uint16_t port;
+ };
+
+ struct v5_response_ipv6
+ {
+ char ip[16];
+ boost::endian::big_uint16_t port;
+ };
+
std::size_t write_domain_header(epee::span<std::uint8_t> out, const std::uint8_t command, const std::uint16_t port, const boost::string_ref domain)
{
if (std::numeric_limits<std::size_t>::max() - sizeof(v4_header) - 2 < domain.size())
@@ -86,6 +207,131 @@ namespace socks
return buf_size;
}
+ std::size_t write_v5_userpass(epee::span<std::uint8_t> out, const user_and_pass& userinfo)
+ {
+ static constexpr const std::uint8_t max_length = std::numeric_limits<std::uint8_t>::max();
+ if (max_length < userinfo.user.size())
+ return 0;
+ if (max_length < userinfo.pass.size())
+ return 0;
+
+ static_assert(max_length < std::numeric_limits<std::size_t>::max());
+ static_assert(max_length < std::numeric_limits<std::size_t>::max() - max_length);
+ static_assert(3 <= std::numeric_limits<std::size_t>::max() - max_length - max_length);
+
+ if (out.size() < 3 + userinfo.user.size() + userinfo.pass.size())
+ return 0;
+
+ const std::size_t initial = out.size();
+
+ out[0] = v5_userpass_version;
+ out[1] = std::uint8_t(userinfo.user.size());
+ out.remove_prefix(2);
+
+ std::memcpy(out.data(), userinfo.user.data(), userinfo.user.size());
+ out.remove_prefix(userinfo.user.size());
+
+ out[0] = std::uint8_t(userinfo.pass.size());
+ out.remove_prefix(1);
+
+ std::memcpy(out.data(), userinfo.pass.data(), userinfo.pass.size());
+ out.remove_prefix(userinfo.pass.size());
+ return initial - out.size();
+ }
+
+ std::array<std::uint16_t, 2> write_v5_initial(epee::span<std::uint8_t> out, const user_and_pass* userinfo)
+ {
+ std::array<std::uint16_t, 2> sizes{{}};
+
+ if (userinfo && (!userinfo->user.empty() || !userinfo->pass.empty()))
+ {
+ const auto header = v5_auth_initial::make();
+ if (out.size() < sizeof(header))
+ return sizes;
+ std::memcpy(out.data(), std::addressof(header), sizeof(header));
+ out.remove_prefix(sizeof(header));
+
+ const std::size_t auth = write_v5_userpass(out, *userinfo);
+ if (!auth)
+ return sizes;
+ out.remove_prefix(auth);
+
+ std::get<0>(sizes) = sizeof(header);
+ std::get<1>(sizes) = auth;
+ }
+ else
+ {
+ const auto header = v5_noauth_initial::make();
+ if (out.size() < sizeof(header))
+ return sizes;
+ std::memcpy(out.data(), std::addressof(header), sizeof(header));
+ out.remove_prefix(sizeof(header));
+
+ std::get<0>(sizes) = sizeof(header);
+ }
+
+ return sizes;
+ }
+
+ template<typename T>
+ std::array<std::uint16_t, 3> write_v5_address_connect(epee::span<std::uint8_t> out, const T& address, const user_and_pass* userinfo)
+ {
+ std::array<std::uint16_t, 3> sizes{{}};
+
+ const auto result = write_v5_initial(out, userinfo);
+ if (!std::get<0>(result))
+ return sizes;
+
+ for (std::size_t length : result)
+ out.remove_prefix(length);
+
+ if (out.size() < sizeof(address))
+ return sizes;
+ std::memcpy(out.data(), std::addressof(address), sizeof(address));
+
+ std::get<0>(sizes) = std::get<0>(result);
+ std::get<1>(sizes) = std::get<1>(result);
+ std::get<2>(sizes) = sizeof(address);
+ return sizes;
+ }
+
+ std::array<std::uint16_t, 3> write_v5_domain_connect(epee::span<std::uint8_t> out, const std::uint16_t port, const boost::string_ref domain, const user_and_pass* userinfo)
+ {
+ std::array<std::uint16_t, 3> sizes{{}};
+ if (std::numeric_limits<std::uint8_t>::max() < domain.size())
+ return sizes;
+
+ const auto result = write_v5_initial(out, userinfo);
+ if (!std::get<0>(result))
+ return sizes;
+
+ for (std::size_t length : result)
+ out.remove_prefix(length);
+
+ const auto request = v5_domain_connect::make(std::uint8_t(domain.size()));
+ static_assert(sizeof(port) <= std::numeric_limits<std::size_t>::max() - sizeof(request));
+ if (std::numeric_limits<std::size_t>::max() - sizeof(request) - sizeof(port) < domain.size())
+ return sizes;
+
+ const std::size_t last_size = sizeof(request) + sizeof(port) + domain.size();
+ if (out.size() < last_size)
+ return sizes;
+
+ std::memcpy(out.data(), std::addressof(request), sizeof(request));
+ out.remove_prefix(sizeof(request));
+
+ std::memcpy(out.data(), domain.data(), domain.size());
+ out.remove_prefix(domain.size());
+
+ const boost::endian::big_uint16_t big_port{port};
+ std::memcpy(out.data(), std::addressof(big_port), sizeof(big_port));
+
+ std::get<0>(sizes) = std::get<0>(result);
+ std::get<1>(sizes) = std::get<1>(result);
+ std::get<2>(sizes) = last_size;
+ return sizes;
+ }
+
struct socks_category : boost::system::error_category
{
explicit socks_category() noexcept
@@ -101,6 +347,23 @@ namespace socks
{
switch (socks::error(value))
{
+ case socks::error::general_failure:
+ return "Socks general server failure";
+ case socks::error::not_allowed:
+ return "Socks connection not allowed by ruleset";
+ case socks::error::network_unreachable:
+ return "Socks network unreachable";
+ case socks::error::host_unreachable:
+ return "Socks host unreachable";
+ case socks::error::connection_refused:
+ return "Socks connection refused";
+ case socks::error::ttl_expired:
+ return "Socks TTL expired";
+ case socks::error::command_not_supported:
+ return "Socks command not supported";
+ case socks::error::address_type_not_supported:
+ return "Socks address type not supported";
+
case socks::error::rejected:
return "Socks request rejected or failed";
case socks::error::identd_connection:
@@ -108,6 +371,8 @@ namespace socks
case socks::error::identd_user:
return "Socks request rejected because the client program and identd report different user-ids";
+ case socks::error::auth_failure:
+ return "Socks authentication failure";
case socks::error::bad_read:
return "Socks boost::async_read read fewer bytes than expected";
case socks::error::bad_write:
@@ -125,6 +390,10 @@ namespace socks
{
switch (socks::error(value))
{
+ case socks::error::network_unreachable:
+ return boost::system::errc::host_unreachable;
+ case socks::error::connection_refused:
+ return boost::system::errc::connection_refused;
case socks::error::bad_read:
case socks::error::bad_write:
return boost::system::errc::io_error;
@@ -158,18 +427,18 @@ namespace socks
if (self_)
{
client& self = *self_;
- self.buffer_size_ = std::min(bytes, sizeof(self.buffer_));
+ std::get<0>(self.buffer_size_) = std::min(bytes, sizeof(self.buffer_));
if (error)
- self.done(error, std::move(self_));
- else if (self.buffer().size() < sizeof(v4_header))
- self.done(socks::error::bad_read, std::move(self_));
+ self.done(error, self_);
+ else if (std::get<0>(self.buffer_size_) < sizeof(v4_header))
+ self.done(socks::error::bad_read, self_);
else if (self.buffer_[0] != 0) // response version
- self.done(socks::error::unexpected_version, std::move(self_));
+ self.done(socks::error::unexpected_version, self_);
else if (self.buffer_[1] != v4_request_granted)
- self.done(socks::error(int(self.buffer_[1]) + 1), std::move(self_));
+ self.done(socks::error(int(self.buffer_[1]) + 1), self_);
else
- self.done(boost::system::error_code{}, std::move(self_));
+ self.done(boost::system::error_code{}, self_);
}
}
};
@@ -181,6 +450,7 @@ namespace socks
static boost::asio::mutable_buffer get_buffer(client& self) noexcept
{
static_assert(sizeof(v4_header) <= sizeof(self.buffer_), "buffer too small for v4 response");
+ std::get<0>(self.buffer_size_) = sizeof(v4_header);
return boost::asio::buffer(self.buffer_, sizeof(v4_header));
}
@@ -190,22 +460,179 @@ namespace socks
{
client& self = *self_;
if (error)
- self.done(error, std::move(self_));
- else if (bytes < self.buffer().size())
- self.done(socks::error::bad_write, std::move(self_));
+ self.done(error, self_);
+ else if (bytes < std::get<0>(self.buffer_size_))
+ self.done(socks::error::bad_write, self_);
else
boost::asio::async_read(self.proxy_, get_buffer(self), boost::asio::bind_executor(self.strand_, completed{std::move(self_)}));
}
}
};
+ struct client::process_v5 : boost::asio::coroutine
+ {
+ std::shared_ptr<client> self_;
+
+ explicit process_v5(std::shared_ptr<client> self)
+ : boost::asio::coroutine(), self_(std::move(self))
+ {}
+
+ static boost::asio::mutable_buffer get_read_buffer(client& self, const std::size_t size)
+ {
+ const std::size_t offset =
+ std::accumulate(self.buffer_size_.begin(), self.buffer_size_.end(), std::size_t(0));
+ if (sizeof(self.buffer_) < offset || sizeof(self.buffer_) - offset < size)
+ throw std::runtime_error{"Not enough room for reading socks v5 buffer"};
+ return boost::asio::buffer(self.buffer_ + offset, size);
+ }
+
+ template<unsigned I>
+ static boost::asio::const_buffer get_write_buffer(const client& self) noexcept
+ {
+ const std::size_t offset =
+ std::accumulate(self.buffer_size_.begin(), self.buffer_size_.begin() + I, std::size_t(0));
+ return boost::asio::buffer(
+ self.buffer_ + offset, std::get<I>(self.buffer_size_)
+ );
+ }
+
+ void operator()(const boost::system::error_code error, std::size_t bytes)
+ {
+ if (!self_)
+ return;
+
+ client& self = *self_;
+ if (error)
+ {
+ self.done(error, self_);
+ return;
+ }
+
+ bool send_userpass = false;
+ BOOST_ASIO_CORO_REENTER(this)
+ {
+ // initial header already written
+
+ BOOST_ASIO_CORO_YIELD boost::asio::async_read(
+ self.proxy_,
+ get_read_buffer(self, sizeof(v5_response_initial)),
+ boost::asio::bind_executor(self.strand_, std::move(*this))
+ );
+ {
+ v5_response_initial header{};
+
+ assert(bytes == sizeof(header));
+ const auto buf = get_read_buffer(self, sizeof(header));
+ std::memcpy(std::addressof(header), buf.data(), sizeof(header));
+ if (header.version != 5)
+ {
+ self.done(socks::error::unexpected_version, self_);
+ return;
+ }
+ if (header.method != v5_noauth_method && header.method != v5_userpass_method)
+ {
+ self.done(socks::error::auth_failure, self_);
+ return;
+ }
+ send_userpass = (header.method == v5_userpass_method);
+ }
+
+ if (send_userpass)
+ {
+ if (!std::get<1>(self.buffer_size_))
+ {
+ self.done(socks::error::auth_failure, self_);
+ return;
+ }
+
+ BOOST_ASIO_CORO_YIELD boost::asio::async_write(
+ self.proxy_, get_write_buffer<1>(self), boost::asio::bind_executor(self.strand_, std::move(*this))
+ );
+ assert(bytes == std::get<1>(self.buffer_size_));
+
+ BOOST_ASIO_CORO_YIELD boost::asio::async_read(
+ self.proxy_,
+ get_read_buffer(self, sizeof(v5_response_auth)),
+ boost::asio::bind_executor(self.strand_, std::move(*this))
+ );
+ {
+ v5_response_auth header{};
+
+ assert(bytes == sizeof(header));
+ const auto buf = get_read_buffer(self, sizeof(header));
+ std::memcpy(std::addressof(header), buf.data(), sizeof(header));
+ if (header.version != v5_userpass_version)
+ {
+ self.done(socks::error::unexpected_version, self_);
+ return;
+ }
+ if (header.status != v5_reply_success)
+ {
+ self.done(socks::error::auth_failure, self_);
+ return;
+ }
+ }
+ }
+
+ BOOST_ASIO_CORO_YIELD boost::asio::async_write(
+ self.proxy_, get_write_buffer<2>(self), boost::asio::bind_executor(self.strand_, std::move(*this))
+ );
+ assert(bytes == std::get<2>(self.buffer_size_));
+
+ self.buffer_size_ = {};
+ BOOST_ASIO_CORO_YIELD boost::asio::async_read(
+ self.proxy_,
+ get_read_buffer(self, sizeof(v5_response_connect)),
+ boost::asio::bind_executor(self.strand_, std::move(*this))
+ );
+ {
+ v5_response_connect header{};
+
+ assert(bytes == sizeof(header));
+ const auto buf = get_read_buffer(self, sizeof(header));
+ std::memcpy(std::addressof(header), buf.data(), sizeof(header));
+ if (header.version != 5)
+ {
+ self.done(socks::error::unexpected_version, self_);
+ return;
+ }
+ if (header.reply != v5_reply_success)
+ {
+ self.done(socks::error(int(header.reply)), self_);
+ return;
+ }
+
+ if (header.type == v5_ipv4_type)
+ bytes = sizeof(v5_response_ipv4);
+ else if (header.type == v5_ipv6_type)
+ bytes = sizeof(v5_response_ipv6);
+ else
+ {
+ self.done(socks::error::unexpected_version, self_);
+ return;
+ }
+ }
+
+ std::get<0>(self.buffer_size_) = sizeof(v5_response_connect);
+ BOOST_ASIO_CORO_YIELD boost::asio::async_read(
+ self.proxy_,
+ get_read_buffer(self, bytes),
+ boost::asio::bind_executor(self.strand_, std::move(*this))
+ );
+ std::get<0>(self.buffer_size_) =
+ std::min(sizeof(self.buffer_), sizeof(v5_response_connect) + bytes);
+ self.done(error, self_);
+ }
+ }
+ };
+
struct client::write
{
std::shared_ptr<client> self_;
static boost::asio::const_buffer get_buffer(client const& self) noexcept
{
- return boost::asio::buffer(self.buffer_, self.buffer_size_);
+ return boost::asio::buffer(self.buffer_, std::get<0>(self.buffer_size_));
}
void operator()(const boost::system::error_code error)
@@ -214,20 +641,24 @@ namespace socks
{
client& self = *self_;
if (error)
- self.done(error, std::move(self_));
+ self.done(error, self_);
+ else if (self.ver_ == version::v5)
+ boost::asio::async_write(self.proxy_, get_buffer(self), boost::asio::bind_executor(self.strand_, process_v5{std::move(self_)}));
else
boost::asio::async_write(self.proxy_, get_buffer(self), boost::asio::bind_executor(self.strand_, read{std::move(self_)}));
}
}
};
+
+
client::client(stream_type::socket&& proxy, socks::version ver)
- : proxy_(std::move(proxy)), strand_(proxy_.get_executor()), buffer_size_(0), buffer_(), ver_(ver)
+ : proxy_(std::move(proxy)), strand_(proxy_.get_executor()), buffer_size_{{}}, buffer_(), ver_(ver)
{}
client::~client() {}
- bool client::set_connect_command(const epee::net_utils::ipv4_network_address& address)
+ bool client::set_connect_command(const epee::net_utils::ipv4_network_address& address, const user_and_pass* userinfo)
{
switch (socks_version())
{
@@ -235,6 +666,13 @@ namespace socks
case version::v4a:
case version::v4a_tor:
break;
+ case version::v5:
+ buffer_size_ = write_v5_address_connect(
+ buffer_,
+ v5_ipv4_connect::make(boost::endian::big_to_native(address.ip()), address.port()),
+ userinfo
+ );
+ return std::get<0>(buffer_size_) != 0;
default:
return false;
}
@@ -242,43 +680,64 @@ namespace socks
static_assert(sizeof(v4_header) < sizeof(buffer_), "buffer size too small for request");
static_assert(0 < sizeof(buffer_), "buffer size too small for null termination");
+ if (userinfo && (!userinfo->user.empty() || !userinfo->pass.empty()))
+ return false;
+
// version 4
const v4_header temp{4, v4_connect_command, address.port(), boost::endian::big_to_native(address.ip())};
std::memcpy(std::addressof(buffer_), std::addressof(temp), sizeof(temp));
buffer_[sizeof(temp)] = 0;
- buffer_size_ = sizeof(temp) + 1;
+
+ buffer_size_ = {};
+ std::get<0>(buffer_size_) = sizeof(temp) + 1;
return true;
}
- bool client::set_connect_command(const boost::string_ref domain, std::uint16_t port)
+ bool client::set_connect_command(const epee::net_utils::ipv6_network_address& address, const user_and_pass* userinfo)
+ {
+ if (socks_version() != version::v5)
+ return false;
+ buffer_size_ = write_v5_address_connect(
+ buffer_, v5_ipv6_connect::make(address.ip(), address.port()), userinfo
+ );
+ return std::get<0>(buffer_size_) != 0;
+ }
+
+ bool client::set_connect_command(const boost::string_ref domain, std::uint16_t port, const user_and_pass* userinfo)
{
switch (socks_version())
{
case version::v4a:
case version::v4a_tor:
break;
-
+ case version::v5:
+ buffer_size_ = write_v5_domain_connect(buffer_, port, domain, userinfo);
+ return std::get<0>(buffer_size_) != 0;
default:
return false;
}
+ if (userinfo && (!userinfo->user.empty() || !userinfo->pass.empty()))
+ return false;
+
const std::size_t buf_used = write_domain_header(buffer_, v4_connect_command, port, domain);
- buffer_size_ = buf_used;
+ buffer_size_ = {};
+ std::get<0>(buffer_size_) = buf_used;
return buf_used != 0;
}
- bool client::set_connect_command(const net::tor_address& address)
+ bool client::set_connect_command(const net::tor_address& address, const user_and_pass* userinfo)
{
if (!address.is_unknown())
- return set_connect_command(address.host_str(), address.port());
+ return set_connect_command(address.host_str(), address.port(), userinfo);
return false;
}
- bool client::set_connect_command(const net::i2p_address& address)
+ bool client::set_connect_command(const net::i2p_address& address, const user_and_pass* userinfo)
{
if (!address.is_unknown())
- return set_connect_command(address.host_str(), address.port());
+ return set_connect_command(address.host_str(), address.port(), userinfo);
return false;
}
@@ -288,13 +747,14 @@ namespace socks
return false;
const std::size_t buf_used = write_domain_header(buffer_, v4tor_resolve_command, 0, domain);
- buffer_size_ = buf_used;
+ buffer_size_ = {};
+ std::get<0>(buffer_size_) = buf_used;
return buf_used != 0;
}
bool client::connect_and_send(std::shared_ptr<client> self, const stream_type::endpoint& proxy_address)
{
- if (self && !self->buffer().empty())
+ if (self && std::get<0>(self->buffer_size_))
{
client& alias = *self;
alias.proxy_.async_connect(proxy_address, boost::asio::bind_executor(alias.strand_, write{std::move(self)}));
@@ -305,10 +765,13 @@ namespace socks
bool client::send(std::shared_ptr<client> self)
{
- if (self && !self->buffer().empty())
+ if (self && std::get<0>(self->buffer_size_))
{
client& alias = *self;
- boost::asio::async_write(alias.proxy_, write::get_buffer(alias), boost::asio::bind_executor(alias.strand_, read{std::move(self)}));
+ if (alias.ver_ == version::v5)
+ boost::asio::async_write(alias.proxy_, write::get_buffer(alias), boost::asio::bind_executor(alias.strand_, process_v5{std::move(self)}));
+ else
+ boost::asio::async_write(alias.proxy_, write::get_buffer(alias), boost::asio::bind_executor(alias.strand_, read{std::move(self)}));
return true;
}
return false;