如何解决带有异步 boost::asio 的 socks4
我正在尝试侵入一个现有的应用程序,socks4 客户端。程序使用异步 boost::asio。
所以到目前为止我已经确定我需要先与socks4服务器协商:
boost::asio::ip::tcp::endpoint socks_proxy{boost::asio::ip::make_address("127.0.0.1"),1080};
if( socks_proxy.protocol() != boost::asio::ip::tcp::v4() )
{
throw boost::system::system_error(
boost::asio::error::address_family_not_supported);
}
....
boost::asio::ip::tcp::socket* m_socket;
// negotiate with the socks server
// m_endpoint is an item in std::queue<boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>> m_endpoints
boost::asio::ip::address_v4::bytes_type address_ = m_endpoint.address().to_v4().to_bytes();
unsigned short port = m_endpoint.port();
unsigned char port_high_byte_ = (port >> 8) & 0xff;
unsigned char port_low_byte_ = port & 0xff;
boost::array<boost::asio::const_buffer,7> send_buffer =
{
{
boost::asio::buffer(&SOCKS_VERSION,1),// const unsigned char SOCKS_VERSION = 0x04;
boost::asio::buffer(&SOCKS_CONNECT,// const unsigned char SOCKS_VERSION = 0x04;
boost::asio::buffer(&port_high_byte_,boost::asio::buffer(&port_low_byte_,boost::asio::buffer(address_),boost::asio::buffer("userid"),boost::asio::buffer(&null_byte_,1). // unsigned char null_byte_ = 0;
}
};
// initiate socks
boost::asio::write( m_socket,send_buffer );
// check it worked
unsigned char status_;
boost::array<boost::asio::mutable_buffer,5> reply_buffer =
{
{
boost::asio::buffer(&null_byte_,boost::asio::buffer(&status_,boost::asio::buffer(&port_high_byte_,boost::asio::buffer(address_)
}
};
boost::asio::read( m_socket,reply_buffer );
if( ! ( null_byte_ == 0 && status_ == 0x5a ) )
{
std::cout << "Proxy connection Failed.\n";
}
但是,现有的应用程序代码基本上可以:
boost::asio::ip::tcp::socket* m_socket;
m_nonsecuresocket = std::make_shared<boost::asio::ip::tcp::socket>(m_io_service);
m_socket = m_nonsecuresocket.get();
m_socket->async_connect(m_endpoint,m_io_strand.wrap(boost::bind(&CLASS::connect_handler,this,_1)));
这样即使我可以编译它,async_connect 仍然会断开套接字。
如何将 socks4 客户端代码集成到 async_connect()
中?
解决方法
正如我所评论的,我认为您的问题需要更多关注。但是,由于这实际上是一个有用的问题,而且最好举个例子,所以我继续实施了一个 socks4::async_proxy_connect
操作:
tcp::socket sock{io};
tcp::endpoint
target({},80),// connect to localhost:http
proxy{{},1080}; // via SOCKS4 proxy on localhost:1080
socks4::async_proxy_connect(sock,target,proxy,handler);
// continue using sock
松散的两端:
-
同步版本尚未实现(但应该更简单)已添加 - 不包括地址解析(正如您的问题)。集成它需要在
boost::asio::async_connect
中进行相当多的需要解析器查询的基础工作。遗憾的是,这并没有很好地考虑重用因素。
列表
-
文件
socks4.hpp
#include <boost/asio.hpp> #include <boost/endian/arithmetic.hpp> namespace socks4 { // 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,rejected_or_failed = 3,need_identd = 4,unconirmed_userid = 5,// 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 "SOCKS4 invalid reply version"; case result_code::rejected_or_failed: return "SOCKS4 rejected or failed"; case result_code::need_identd: return "SOCKS4 unreachable (client not running identd)"; case result_code::unconirmed_userid: return "SOCKS4 identd could not confirm user ID"; case result_code::failed: return "SOCKS4 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 socks4 template <> struct boost::system::is_error_code_enum<socks4::result_code> : std::true_type {}; namespace socks4 { using namespace std::placeholders; template <typename Endpoint> struct core_t { Endpoint _target; Endpoint _proxy; core_t(Endpoint target,Endpoint proxy) : _target(target),_proxy(proxy) {} #pragma pack(push) #pragma pack(1) using ipv4_octets = boost::asio::ip::address_v4::bytes_type; using net_short = boost::endian::big_uint16_t; struct alignas(void*) Req { uint8_t version = 0x04; uint8_t cmd = 0x01; net_short port; ipv4_octets address; } _request{0x04,0x01,_target.port(),_target.address().to_v4().to_bytes()}; struct alignas(void*) Res { uint8_t reply_version; uint8_t status; net_short port; ipv4_octets address; } _response; #pragma pack(pop) using const_buffer = boost::asio::const_buffer; using mutable_buffer = boost::asio::mutable_buffer; auto request_buffers(char const* szUserId) const { return std::array<const_buffer,2>{ boost::asio::buffer(&_request,sizeof(_request)),boost::asio::buffer(szUserId,strlen(szUserId) + 1)}; } auto response_buffers() { return boost::asio::buffer(&_response,sizeof(_response)); } error_code get_result(error_code ec = {}) const { if (ec) return ec; if (_response.reply_version != 0) return result_code::invalid_version; switch (_response.status) { case 0x5a: return result_code::ok; // Request grantd case 0x5B: return result_code::rejected_or_failed; case 0x5C: return result_code::need_identd; case 0x5D: return result_code::unconirmed_userid; } return result_code::failed; } }; template <typename Socket,typename Completion> struct async_proxy_connect_op { using Endpoint = typename Socket::protocol_type::endpoint; using executor_type = typename Socket::executor_type; auto get_executor() { return _socket.get_executor(); } private: core_t<Endpoint> _core; Socket& _socket; std::string _userId; Completion _handler; public: async_proxy_connect_op(Completion handler,Socket& s,Endpoint target,Endpoint proxy,std::string user_id = {}) : _core(target,proxy),_socket(s),_userId(std::move(user_id)),_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 SENT{}; struct ONRESPONSE{}; 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.request_buffers(_userId.c_str()),SENT{},_1,_2)); } void operator()(Self& self,SENT,error_code ec,size_t xfer) { if (ec) return _handler(ec); auto buf = _core.response_buffers(); boost::asio::async_read( _socket,buf,boost::asio::transfer_exactly(buf.size()),ONRESPONSE{},ONRESPONSE,size_t xfer) { _handler(_core.get_result(ec)); } }; template <typename Socket,typename Endpoint = typename Socket::protocol_type::endpoint> error_code proxy_connect(Socket& s,Endpoint ep,std::string const& user_id,error_code& ec) { core_t<Endpoint> core(ep,proxy); ec.clear(); s.connect(core._proxy,ec); if (!ec) boost::asio::write(s,core.request_buffers(user_id.c_str()),ec); auto buf = core.response_buffers(); if (!ec) boost::asio::read(s,core.response_buffers(),ec); return ec = core.get_result(ec); } template <typename Socket,typename Endpoint = typename Socket::protocol_type::endpoint> void proxy_connect(Socket& s,std::string const& user_id = "") { error_code ec; if (proxy_connect(s,ep,user_id,ec)) throw system_error(ec); } template <typename Socket,typename Token,typename Endpoint = typename Socket::protocol_type::endpoint> auto async_proxy_connect(Socket& s,std::string user_id,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,std::move(user_id)); self->init(std::move(self)); return result.get(); } template <typename Socket,Token&& token) { return async_proxy_connect<Socket,Token,Endpoint>( s,"",std::forward<Token>(token)); } } // namespace socks4
演示
-
文件
test.cpp
#include "socks4.hpp" #include <boost/beast.hpp> #include <boost/beast/http.hpp> #include <iostream> int main(int argc,char**) { bool synchronous = argc > 1; using boost::asio::ip::tcp; boost::asio::thread_pool ctx(1); // just one thread will do tcp::socket sock{ctx}; tcp::endpoint target( boost::asio::ip::address_v4::from_string("173.203.57.63"),proxy{{},1080}; try { if (synchronous) { std::cerr << "Using synchronous interface" << std::endl; socks4::proxy_connect(sock,proxy); // throws system_error if failed } else { std::cerr << "Using asynchronous interface" << std::endl; // using the async interface (still emulating synchronous by using // future for brevity of this demo) auto fut = socks4::async_proxy_connect(sock,boost::asio::use_future); fut.get(); // throws system_error if failed } // Now do a request using beast namespace beast = boost::beast; namespace http = beast::http; { http::request<http::empty_body> req(http::verb::get,"/",11); req.set(http::field::host,"coliru.stacked-crooked.com"); req.set(http::field::connection,"close"); std::cout << "-------\nRequest: " << req << "\n-------\n"; http::write(sock,req); } { http::response<http::string_body> res; beast::flat_buffer buf; http::read(sock,res); std::cout << "\n-------\nResponse: " << res << "\n"; } } catch(socks4::system_error const& se) { std::cerr << "Error: " << se.code().message() << std::endl; } ctx.join(); }
输出
Using asynchronous interface
-------
Request: GET / HTTP/1.1
Host: coliru.stacked-crooked.com
Connection: close
-------
-------
Response: HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 8616
Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) OpenSSL/1.0.2g
Date: Thu,29 Apr 2021 19:05:03 GMT
Connection: close
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<html>
<head>
<title>Coliru</title>
(其余回复省略)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。