Boost.Socks Logo

PrevUpHomeNext

Async SOCKS Server

This is a reference implementation of a SOCKS server. It can be used to validate the library functionality:

File: socks_server_async.cpp

#include <boost/asio/signal_set.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ip/udp.hpp>

#include <boost/core/detail/string_view.hpp>

#include <cstdlib>
#include <iostream>
#include <string>
#include <memory>
#include <utility>

namespace asio = boost::asio;
namespace ip = boost::asio::ip;
using tcp = boost::asio::ip::tcp;
using endpoint = boost::asio::ip::tcp::endpoint;
using error_code = boost::system::error_code;

//------------------------------------------------------------------------------

// A small version of std::to_string to avoid a
// common bug on MinGw.
std::string
to_string(std::uint16_t v)
{
#if (defined(__MINGW32__) || defined(MINGW32) || defined(BOOST_MINGW32))
    constexpr int bn = 4 * sizeof(std::uint16_t);
    char str[bn];
    int n = std::snprintf(str, bn, "%d", v);
    BOOST_ASSERT(n <= bn);
    boost::ignore_unused(n);
    return std::string(str);
#else
    return std::to_string(v);
#endif
}

class socks_connection
    : public std::enable_shared_from_this<socks_connection>
{
public:
    using pointer = std::shared_ptr<socks_connection>;

    static
    pointer
    create(boost::asio::io_context& io_context)
    {
        return pointer(new socks_connection(io_context));
    }

    tcp::socket&
    socket()
    {
        return socket_;
    }

    void
    start()
    {
        std::cout <<
            "Client " << socket_.remote_endpoint() << "\n";
        auto self(shared_from_this());
        /*
         * Read a
         * - SOCKS5 greeting, or
         * - SOCKS4 connect
         */
        asio::async_read(
            socket_,
            asio::buffer(buffer_),
            [this](const error_code& ec, std::size_t n)
            {
                // Stop reading at invalid version
                if (ec.failed()
                    || (n > 0 &&
                        buffer_[0] != 0x05
                        && buffer_[0] != 0x04))
                    return std::size_t(0);

                // SOCKS4 Connect
                // - VER | CMD | DSTPORT | DSTIP | USERID | NULLs
                if (buffer_[0] == 0x04)
                {
                    // USERID ends with NULL
                    // We read from until we find a NULL,
                    // with a limit of buffer_.size()
                    // which means a username of
                    // up to 250 chars
                    const std::size_t maxn = buffer_.size();
                    // Check CMD
                    if (n >= 1 && buffer_[1] != 0x01)
                        return std::size_t(0);
                    // Anything is valid for the next
                    // 6 bytes DSTPORT | DSTIP, so we
                    // look for NULL after buffer_[8]
                    // to stop reading
                    if (n >= 9)
                        for (std::size_t i = 8; i < n; ++i)
                            if (buffer_[i] == 0x00)
                                return std::size_t(0);
                    // Keep allowing the max otherwise
                    return maxn - n;
                }

                // SOCKS5 Greeting
                // - VER / NMETHODS / METHODS
                // Read all methods
                if (n >= 2)
                {
                    std::size_t nmethods = buffer_[1];
                    std::size_t greet_n = 2 + nmethods;
                    if (n < greet_n)
                        return greet_n - n;
                    return std::size_t(0);
                }
                // If n < 2, we can read up to the
                // max number of bytes on next
                // read, assuming a greeting with
                // 256 methods
                return std::size_t(258) - n;
            },
            [this, self](error_code ec, std::size_t n)
            {
                if (!ec.failed())
                {
                    buffer_.resize(n);
                    if (buffer_[0] == 0x05)
                        return parse_socks_greeting();
                    else if (buffer_[0] == 0x04)
                        return parse_socks_connect_v4();
                }
                fail(ec, "Failure to read SOCKS greeting");
            }
        );
    }

private:
    explicit
    socks_connection(asio::io_context& ioc)
        : ioc_(ioc),
          socket_(ioc),
          buffer_(258, 0x00),
          target_socket_(ioc),
          resolver_(ioc)
    {
    }

    void
    parse_socks_greeting()
    {
        // VER / NMETHODS / METHODS
        if (buffer_.empty())
            return fail(
                asio::error::message_size,
                "Empty SOCKS greeting");
        if (buffer_[0] != 0x05)
            return fail(
                asio::error::no_protocol_option,
                "Bad SOCKS version");
        if (buffer_.size() == 1)
            return fail(
                asio::error::message_size,
                "Empty SOCKS auth methods");
        if (buffer_[1] != buffer_.size() - 2)
            return fail(
                asio::error::message_size,
                "Missing SOCKS auth methods");

        // At this point, the range [buffer[2], ...]
        // contains the possible authentication methods
        // We attempt 0x02 (userpass) or 0x00 (no auth)
        for (std::size_t i = 2; i < buffer_.size(); ++i)
        {
            if (buffer_[i] == 0x02)
            {
                server_choice_ = 0x02;
                return do_server_choice();
            }
        }

        for (std::size_t i = 2; i < buffer_.size(); ++i)
        {
            if (buffer_[i] == 0x00)
            {
                server_choice_ = 0x00;
                return do_server_choice();
            }
        }

        // No acceptable method
        server_choice_ = 0xFF;
        do_server_choice();
    }

    void
    do_server_choice()
    {
        buffer_ = {0x05, server_choice_};
        auto self(shared_from_this());
        asio::async_write(
            socket_,
            asio::buffer(buffer_),
            [this, self](error_code ec, std::size_t)
            {
                if (ec.failed())
                    fail(ec, "Cannot write greeting response");
                else if (self->server_choice_ == 0xFF)
                    fail(ec, "No compatible client auth method");
                else if (self->server_choice_ == 0x02)
                    do_userpass_greet();
                else
                    do_read_connect_request();
            }
        );
    }

    void
    do_userpass_greet()
    {
        buffer_.resize(513);
        auto self(shared_from_this());
        asio::async_read(
            socket_,
            asio::buffer(buffer_),
            [this](const error_code& ec, std::size_t n)
            {
                if (ec.failed())
                    return std::size_t(0);
                // Userpass greeting
                constexpr std::size_t nmax = 513;
                // VER | IDLEN | ID  | PWLEN | PW
                // Stop reading at invalid version
                if (n >= 1 && buffer_[0] != 0x01)
                    return std::size_t(0);
                // Read at most nmax when we don't
                // know the IDLEN
                if (n < 2)
                    return nmax;
                std::size_t idlen = buffer_[1];
                std::size_t pwlen = 256;
                if (n > 2 + idlen)
                    pwlen = buffer_[2+idlen];
                return 3 + idlen + pwlen;
            },
            [this, self](error_code ec, std::size_t n)
            {
                if (!ec.failed())
                {
                    buffer_.resize(n);
                    parse_userpass_greeting();
                }
                else
                {
                    fail(ec, "Failure to read SOCKS userpass greeting");
                }
            }
        );
    }

    void
    parse_userpass_greeting()
    {
        // VER | IDLEN | ID  | PWLEN | PW
        if (buffer_.empty())
            return fail(
                asio::error::message_size,
                "Empty userpass greeting");
        if (buffer_[0] != 0x01)
            return fail(
                asio::error::no_protocol_option,
                "Bad userpass version");
        if (buffer_.size() == 1)
            return fail(
                asio::error::message_size,
                "Missing SOCKS username");
        std::size_t idlen = buffer_[1];
        if (buffer_.size() < idlen + 2)
            return fail(
                asio::error::message_size,
                "Incomplete SOCKS username");
        if (buffer_.size() < idlen + 3)
            return fail(
                asio::error::message_size,
                "Missing SOCKS password");
        std::size_t passlen = buffer_[2 + idlen];
        if (buffer_.size() < idlen + 3 + passlen)
            return fail(
                asio::error::message_size,
                "Incomplete SOCKS password");

        // Message is OK
        boost::core::string_view username(
            reinterpret_cast<char*>(buffer_.data()) + 2,
            idlen);
        boost::core::string_view password(
            reinterpret_cast<char*>(buffer_.data()) + 2 + idlen,
            passlen);
        std::cout <<
            "Username: " << username <<
            "Password: " << password << "\n";

        // This server implementation accepts
        // any username and password, so we send
        // a successful reply
        // VER | STATUS
        buffer_ = {0x01, 0x00};
        auto self(shared_from_this());
        asio::async_write(
            socket_,
            asio::buffer(buffer_),
            [this, self](error_code ec, std::size_t)
            {
                if (ec.failed())
                    fail(ec, "Cannot write userpass response");
                else
                    do_read_connect_request();
            }
        );
    }

    void
    do_read_connect_request()
    {
        buffer_.resize(263);
        auto self(shared_from_this());
        asio::async_read(
            socket_,
            asio::buffer(buffer_),
            [this](const error_code& ec, std::size_t n)
            {
                // Greeting
                // VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT
                constexpr std::size_t nmax = 263;
                // Stop reading at invalid VER,
                // CMD, or RSV.
                if (ec.failed()
                    || (n >= 1 && buffer_[0] != 0x05)
                    || (n >= 2 && buffer_[1] != 0x01)
                    || (n >= 3 && buffer_[2] != 0x00))
                    return std::size_t(0);
                if (n < 4)
                    return nmax - n;
                std::size_t req_n;
                switch (buffer_[3])
                {
                case 0x01:
                    req_n = 10;
                    break;
                case 0x03:
                {
                    std::size_t addr_n = 256;
                    if (n > 4)
                        addr_n = buffer_[4];
                    req_n = 7 + addr_n;
                    break;
                }
                case 0x04:
                    req_n = 22;
                    break;
                default:
                    req_n = 0;
                    break;
                }
                if (n < req_n)
                    return req_n - n;
                return std::size_t(0);
            },
            [this, self](error_code ec, std::size_t n)
            {
                if (!ec.failed())
                {
                    buffer_.resize(n);
                    return parse_socks_connect();
                }
                fail(ec, "Failure to read SOCKS connect");
            }
        );
    }

    void
    parse_socks_connect()
    {
        // VER | IDLEN | ID  | PWLEN | PW
        if (buffer_.empty())
            return fail(
                asio::error::message_size,
                "Empty SOCKS connect");
        if (buffer_[0] != 0x05)
        {
            fail(
                asio::error::no_protocol_option,
                "Bad SOCKS request version");
            // connection not allowed by ruleset
            return do_connect_reply(0x02);
        }
        if (buffer_.size() == 1)
        {
            fail(
                asio::error::message_size,
                "Missing SOCKS command");
            // connection not allowed by ruleset
            return do_connect_reply(0x02);
        }
        if (buffer_[1] != 0x01)
        {
            fail(
                asio::error::message_size,
                "Invalid SOCKS connect command");
            // Command not supported
            return do_connect_reply(0x07);
        }
        if (buffer_.size() == 2)
        {
            fail(
                asio::error::message_size,
                "Missing SOCKS reserved byte");
            // connection not allowed by ruleset
            return do_connect_reply(0x02);
        }
        if (buffer_[2] != 0x00)
        {
            fail(
                asio::error::message_size,
                "Invalid SOCKS reserved byte");
            // connection not allowed by ruleset
            return do_connect_reply(0x02);
        }
        if (buffer_.size() == 3)
        {
            fail(
                asio::error::message_size,
                "Missing SOCKS address type");
            // connection not allowed by ruleset
            return do_connect_reply(0x02);
        }
        if (buffer_[3] != 0x01
            && buffer_[3] != 0x03
            && buffer_[3] != 0x04)
        {
            fail(
                asio::error::message_size,
                "Invalid SOCKS address type");
            // Address type not supported
            return do_connect_reply(0x08);
        }
        if (buffer_[3] == 0x01)
        {
            if (buffer_.size() == 10)
            {
                asio::ip::address_v4::bytes_type addr;
                addr[0] = buffer_[4];
                addr[1] = buffer_[5];
                addr[2] = buffer_[6];
                addr[3] = buffer_[7];
                auto ip = ip::make_address_v4(addr);
                std::uint16_t port = 0;
                port |= buffer_[8];
                port <<= 8;
                port |= buffer_[9];
                target_ = {ip, port};
                do_target_connect();
            }
            else
            {
                fail(
                    asio::error::message_size,
                    "Invalid SOCKS ipv4 address");
                // Address type not supported
                return do_connect_reply(0x08);
            }
        }
        else if (buffer_[3] == 0x04)
        {
            if (buffer_.size() == 22)
            {
                ip::address_v6::bytes_type addr;
                for (std::size_t i = 0; i < 16; ++i)
                    addr[i] = buffer_[4 + i];
                auto ip = ip::make_address_v6(addr);
                std::uint16_t port = 0;
                port |= buffer_[20];
                port <<= 8;
                port |= buffer_[21];
                target_ = {ip, port};
                do_target_connect();
            }
            else
            {
                fail(
                    asio::error::message_size,
                    "Invalid SOCKS ipv6 address");
                // Address type not supported
                return do_connect_reply(0x08);
            }
        }
        else if (buffer_[3] == 0x03)
        {
            if (buffer_.size() == std::size_t(7) + buffer_[4])
            {
                std::string host(
                    buffer_.data() + 5,
                    buffer_.data() + 5 + buffer_[4]);
                std::uint16_t port = buffer_[buffer_.size() - 2];
                port <<= 8;
                port |= buffer_[buffer_.size() - 1];
                std::string service = to_string(port);
                auto self(shared_from_this());
                resolver_.async_resolve(
                    host,
                    service,
                    [this, self](
                        error_code ec,
                        tcp::resolver::results_type eps)
                    {
                        if (ec.failed())
                        {
                            std::string host(
                                buffer_.data() + 5,
                                buffer_.data() + 5 + buffer_[4]);
                            fail(
                                ec,
                                "Cannot resolve domain name",
                                host);
                            // Host unreachable
                            return do_connect_reply(0x04);
                        }
                        target_ = eps.begin()->endpoint();
                        do_target_connect();
                    }
                );
            }
            else
            {
                fail(asio::error::message_size,
                     "Invalid SOCKS domain address");
                // Address type not supported
                do_connect_reply(0x08);
            }
        }
    }

    void
    parse_socks_connect_v4()
    {
        // VER | CMD | DSTPORT | DSTIP | USERID | NULL
        if (buffer_.empty())
            return fail(
                asio::error::message_size,
                "Empty SOCKS connect");
        if (buffer_[0] != 0x04)
        {
            fail(
                asio::error::no_protocol_option,
                "Bad request version");
            // request rejected or failed
            return do_connect_reply_v4(91);
        }
        if (buffer_.size() == 1)
        {
            fail(
                asio::error::message_size,
                "Missing SOCKS command");
            // request rejected or failed
            return do_connect_reply_v4(91);
        }
        if (buffer_[1] != 0x01)
        {
            fail(
                asio::error::message_size,
                "Invalid SOCKS connect command");
            // request rejected or failed
            return do_connect_reply_v4(91);
        }
        if (buffer_.size() >= 8)
        {
            std::uint16_t port = 0;
            port |= buffer_[2];
            port <<= 8;
            port |= buffer_[3];
            asio::ip::address_v4::bytes_type addr;
            addr[0] = buffer_[4];
            addr[1] = buffer_[5];
            addr[2] = buffer_[6];
            addr[3] = buffer_[7];
            auto ip = ip::make_address_v4(addr);
            target_ = {ip, port};
            /*
             * [buffer[8], ...] contain the
             * ident id, which this server
             * ignores
             */
            return do_target_connect_v4();
        }
        else
        {
            fail(
                asio::error::message_size,
                "Invalid SOCKS ipv4 address");
            // request rejected or failed
                return do_connect_reply_v4(91);
        }
    }

    void
    do_target_connect()
    {
        auto self(shared_from_this());
        target_socket_.async_connect(
            target_,
            [this, self](error_code ec)
            {
                if (ec.failed())
                {
                    fail(
                        ec,
                        "Cannot connect to target",
                        target_);
                    // Host unreachable
                    return do_connect_reply(0x04);
                }
                // succeeded
                do_connect_reply(0x00);
            }
        );
    }

    void
    do_target_connect_v4()
    {
        auto self(shared_from_this());
        target_socket_.async_connect(
            target_,
            [this, self](error_code ec)
            {
                if (ec.failed())
                {
                    fail(
                        ec,
                        "Cannot connect to target",
                        target_);
                    // request rejected or failed
                    return do_connect_reply_v4(91);
                }
                // request granted
                return do_connect_reply_v4(90);
            }
        );
    }

    void
    do_connect_reply(unsigned char rep)
    {
        endpoint bnd_ep =
            target_socket_.local_endpoint();
        if (rep != 0x00)
        {
            buffer_.resize(10);
            // VER
            buffer_[0] = 0x05;
            // REP
            buffer_[1] = rep;
            // RSV
            buffer_[2] = 0x00;
            // ATYP
            buffer_[3] = 0x01;
            // DST.ADDR
            buffer_[4] = 0x00;
            buffer_[5] = 0x00;
            buffer_[6] = 0x00;
            buffer_[7] = 0x00;
            // DST.PORT
            buffer_[8] = 0x00;
            buffer_[9] = 0x00;
        }
        else if (bnd_ep.address().is_v4())
        {
            buffer_.resize(10);
            // VER
            buffer_[0] = 0x05;
            // REP
            buffer_[1] = rep;
            // RSV
            buffer_[2] = 0x00;
            // ATYP
            buffer_[3] = 0x01;
            // DST.ADDR
            auto bnd_v4 = bnd_ep.address().to_v4();
            auto bnd_bytes = bnd_v4.to_bytes();
            buffer_[4] = bnd_bytes[0];
            buffer_[5] = bnd_bytes[1];
            buffer_[6] = bnd_bytes[2];
            buffer_[7] = bnd_bytes[3];
            // DST.PORT
            std::uint16_t p = bnd_ep.port();
            buffer_[8] = (p >> 8) & 0xFF;
            buffer_[9] = p & 0xFF;
        }
        else /* if (bnd_ep.address().is_v6()) */
        {
            buffer_.resize(22);
            // VER
            buffer_[0] = 0x05;
            // REP
            buffer_[1] = rep;
            // RSV
            buffer_[2] = 0x00;
            // ATYP
            buffer_[3] = 0x04;
            // DST.ADDR
            auto bnd_v6 = bnd_ep.address().to_v6();
            auto bnd_bytes = bnd_v6.to_bytes();
            for (std::size_t i = 0; i < 16; ++i)
                buffer_[i + 4] = bnd_bytes[i];
            // DST.PORT
            std::uint16_t p = bnd_ep.port();
            buffer_[20] = (p >> 8) & 0xFF;
            buffer_[21] = p & 0xFF;
        }
        auto self(shared_from_this());
        asio::async_write(
            socket_,
            asio::buffer(buffer_),
            [this, self](error_code ec, std::size_t)
            {
                if (ec.failed())
                    return fail(
                        ec, "Cannot write connect reply");
                unsigned char rep = buffer_[1];
                if (rep == 0x00)
                    do_relay();
                else
                    socket_.close();
            }
        );
    }

    void
    do_connect_reply_v4(unsigned char rep)
    {
        endpoint bnd_ep =
            target_socket_.local_endpoint();
        buffer_.resize(8);
        // VER
        buffer_[0] = 0x04;
        // REP
        buffer_[1] = rep;
        if (rep != 90)
        {
            // DST.PORT
            buffer_[2] = 0x00;
            buffer_[3] = 0x00;
            // DST.ADDR
            buffer_[4] = 0x00;
            buffer_[5] = 0x00;
            buffer_[6] = 0x00;
            buffer_[7] = 0x00;
        }
        else
        {
            // DST.PORT
            std::uint16_t p = bnd_ep.port();
            buffer_[2] = (p >> 8) & 0xFF;
            buffer_[3] = p & 0xFF;
            // DST.ADDR
            if (bnd_ep.address().is_v4())
            {
                auto bnd_v4 = bnd_ep.address().to_v4();
                auto bnd_bytes = bnd_v4.to_bytes();
                buffer_[4] = bnd_bytes[0];
                buffer_[5] = bnd_bytes[1];
                buffer_[6] = bnd_bytes[2];
                buffer_[7] = bnd_bytes[3];
            }
            else
            {
                buffer_[4] = 0x00;
                buffer_[5] = 0x00;
                buffer_[6] = 0x00;
                buffer_[7] = 0x00;
            }
        }
        auto self(shared_from_this());
        asio::async_write(
            socket_,
            asio::buffer(buffer_),
            [this, self](error_code ec, std::size_t)
            {
                if (ec.failed())
                    return fail(
                        ec, "Cannot write connect reply");
                unsigned char v = buffer_[0];
                unsigned char rep = buffer_[1];
                if ((v == 0x05
                     && rep == 0x00)
                    || (v == 0x04
                        && rep == 90))
                    do_relay();
                else
                    socket_.close();
            }
        );
    }

    void
    do_relay()
    {
        // Relay anything from client to target
        // and vice-versa
        do_relay_from_client();
        do_relay_from_target();
    }

    void
    do_relay_from_client()
    {
        buffer_.resize(1024);
        auto self(shared_from_this());
        socket_.async_read_some(
            asio::buffer(buffer_),
            [this, self](error_code ec, std::size_t n)
            {
                if (ec == asio::error::eof)
                    return fail(ec, "Client disconnected");
                else if (ec.failed())
                    return fail(ec, "Cannot read from client");
                buffer_.resize(n);
                do_relay_to_target();
            }
        );
    }

    void
    do_relay_to_target()
    {
        auto self(shared_from_this());
        target_socket_.async_write_some(
            asio::buffer(buffer_),
            [this, self](error_code ec, std::size_t)
            {
                if (ec.failed())
                    return fail(ec, "Cannot write to target");
                do_relay_from_client();
            }
        );
    }

    void
    do_relay_from_target()
    {
        target_buffer_.resize(1024);
        auto self(shared_from_this());
        target_socket_.async_read_some(
            asio::buffer(target_buffer_),
            [this, self](error_code ec, std::size_t n)
            {
                if (ec.failed())
                    return fail(ec, "Application server disconnected");
                else if (ec.failed())
                    return fail(ec, "Cannot read from target");
                target_buffer_.resize(n);
                do_relay_to_client();
            }
        );
    }

    void
    do_relay_to_client()
    {
        auto self(shared_from_this());
        socket_.async_write_some(
            asio::buffer(target_buffer_),
            [this, self](error_code ec, std::size_t)
            {
                if (ec.failed())
                    return fail(ec, "Cannot write to client");
                do_relay_from_target();
            }
        );
    }

    static
    void
    fail(error_code ec, char const* what)
    {
        std::cerr << what << ": " << ec.message() << "\n";
    }

    template <class T>
    static
    void
    fail(error_code ec, char const* what, T const& t)
    {
        std::cerr << what << ": " << ec.message() << ": " << t << "\n";
    }

    asio::io_context& ioc_;
    tcp::socket socket_;
    std::vector<unsigned char> buffer_;
    std::vector<unsigned char> target_buffer_;
    unsigned char server_choice_{0x00};
    endpoint target_{};
    tcp::socket target_socket_;
    tcp::resolver resolver_;
};

class socks_server
{
public:
    socks_server(
        asio::io_context& io_context,
        std::string const& listen_address,
        std::string const& listen_port)
        : ioc_(io_context),
          listen_endpoint_(*tcp::resolver(ioc_).resolve(
            listen_address,
            listen_port,
            tcp::resolver::passive)),
          acceptor_(io_context, listen_endpoint_)
    {
        std::cout << "Listening on " << listen_endpoint_ << "\n";
        do_accept();
    }

private:
    void do_accept()
    {
        socks_connection::pointer new_connection =
            socks_connection::create(ioc_);
        acceptor_.async_accept(
            new_connection->socket(),
            [this, new_connection](
                const boost::system::error_code& ec) {
                if (!ec.failed())
                    new_connection->start();
                do_accept();
            });
    }

    boost::asio::io_context& ioc_;
    tcp::endpoint listen_endpoint_;
    tcp::acceptor acceptor_;
};


int main(int argc, char** argv)
{
    // Check command line arguments.
    std::string listen_address = "localhost";
    std::string listen_port = "1080";
    if (argc != 3)
    {
        std::cerr <<
            "Usage: socks_server_async <listen address> <listen port>\n\n"
            "Example:\n"
            "    socks_server_async localhost 1080\n"
            "Using default values:\n"
            "    - <listen address>: localhost\n"
            "    - <listen port>: 1080\n\n";
        if (argc >= 2)
            listen_address = argv[1];
        if (argc > 3)
            listen_port = argv[2];
    }
    else
    {
        listen_address = argv[1];
        listen_port = argv[2];
    }

    try
    {
        asio::io_context ioc;
        asio::signal_set signals(ioc, SIGINT, SIGTERM);
        signals.async_wait(
            [&ioc](error_code const&, std::size_t)
            {
                ioc.stop();
            }
        );
        socks_server server(
            ioc, listen_address, listen_port);
        ioc.run();
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return EXIT_SUCCESS;
}

PrevUpHomeNext