带有异步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, 1), // const unsigned char SOCKS_VERSION = 0x04;
        boost::asio::buffer(&port_high_byte_, 1),
        boost::asio::buffer(&port_low_byte_, 1),
        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_, 1),
        boost::asio::buffer(&status_, 1),
        boost::asio::buffer(&port_high_byte_, 1),
        boost::asio::buffer(&port_low_byte_, 1),
        boost::asio::buffer(address_)
      }
    };
    boost::asio::read( m_socket, reply_buffer );
    
    if( ! ( null_byte_ == 0 && status_ == 0x5a ) )
    {
        std::cout << "Proxy connection failed.
";
    }
    

然而,现有应用程序代码基本上做到了:

    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中做大量的基础工作,而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()),
                     std::bind(Binder{std::move(self)}, 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(buffer_size(buf)),
                     std::bind(Binder{std::move(self)}, ONRESPONSE{}, _1, _2));
             }
    
             void operator()(Self& self, ONRESPONSE, error_code ec, 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, Endpoint proxy,
                                  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(),
                                   boost::asio::transfer_exactly(buffer_size(buf)), ec);
    
             return ec = core.get_result(ec);
         }
    
         template <typename Socket,
                   typename Endpoint = typename Socket::protocol_type::endpoint>
         void proxy_connect(Socket& s, Endpoint ep, Endpoint proxy,
                            std::string const& user_id = "") {
             error_code ec;
             if (proxy_connect(s, ep, proxy, 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, Endpoint ep, Endpoint proxy,
                            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, ep, proxy, std::move(user_id));
             self->init(std::move(self));
             return result.get();
         }
    
         template <typename Socket, typename Token,
                   typename Endpoint = typename Socket::protocol_type::endpoint>
         auto async_proxy_connect(Socket& s, Endpoint ep, Endpoint proxy, Token&& token) {
             return async_proxy_connect<Socket, Token, Endpoint>(
                 s, ep, proxy, "", 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"), 80),
             proxy{{}, 1080};
    
         try {
             if (synchronous) {
                 std::cerr << "Using synchronous interface" << std::endl;
                 socks4::proxy_connect(sock, target,
                                       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, target, proxy,
                                                        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 << "-------
    Request: " << req << "
    -------
    ";
                 http::write(sock, req);
             }
    
             {
                 http::response<http::string_body> res;
                 beast::flat_buffer                buf;
    
                 http::read(sock, buf, res);
                 std::cout << "
    -------
    Response: " << res << "
    ";
             }
         } 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>

(省略响应的其余部分)

相关文章