带有异步 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>

(其余回复省略)

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...