aboutsummaryrefslogtreecommitdiff
path: root/src/net/socks.h
blob: 8259377924263d383537a6fb07bc09faccb5a95e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright (c) 2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#pragma once

#include <cstdint>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include <boost/utility/string_ref.hpp>
#include <memory>
#include <utility>

#include "net/fwd.h"
#include "span.h"

namespace epee
{
namespace net_utils
{
    class ipv4_network_address;
}
}

namespace net
{
namespace socks
{
    //! Supported socks variants.
    enum class version : std::uint8_t
    {
        v4 = 0,
        v4a,
        v4a_tor  //!< Extensions defined in Tor codebase
    };

    //! Possible errors with socks communication. Defined in https://www.openssh.com/txt/socks4.protocol
    enum class error : int
    {
        // 0 is reserved for success value
        // 1-256 -> reserved for error values from socks server (+1 from wire value).
        rejected = 92,
        identd_connection,
        identd_user,
        // Specific to application
        bad_read = 257,
        bad_write,
        unexpected_version
    };

    /* boost::system::error_code is extended for easier compatibility with
       boost::asio errors. If std::error_code is needed (with expect<T> for
       instance), then upgrade to boost 1.65+ or use conversion code in
       develop branch at boost/system/detail/std_interoperability.hpp */

    //! \return boost::system::error_category for net::socks namespace
    const boost::system::error_category& error_category() noexcept;

    //! \return net::socks::error as a boost::system::error_code.
    inline boost::system::error_code make_error_code(error value) noexcept
    {
        return boost::system::error_code{int(value), socks::error_category()};
    }

    //! Client support for socks connect and resolve commands.
    class client
    {
        boost::asio::ip::tcp::socket proxy_;
        std::uint16_t buffer_size_;
        std::uint8_t buffer_[1024];
        socks::version ver_;

        /*!
            Only invoked after `*send(...)` function completes or fails.
            `bool(error) == false` indicates success; `self.get()` is always
            `this` and allows implementations to skip
            `std::enable_shared_from_this<T>` (ASIO callbacks need shared_ptr).
            The design saves space and reduces cycles (everything uses moves,
            so no atomic operations are ever necessary).

            \param error when processing last command (if any).
            \param self `shared_ptr<client>` handle to `this`.
         */
        virtual void done(boost::system::error_code error, std::shared_ptr<client> self) = 0;

    public:
        using stream_type = boost::asio::ip::tcp;

        // defined in cpp
        struct write;
        struct read;
        struct completed;

        /*!
            \param proxy ownership is passed into `this`. Does not have to be
                in connected state.
            \param ver socks version for the connection.
        */
        explicit client(stream_type::socket&& proxy, socks::version ver);

        client(const client&) = delete;
        virtual ~client();
        client& operator=(const client&) = delete;

        //! \return Ownership of socks client socket object.
        stream_type::socket take_socket()
        {
            return stream_type::socket{std::move(proxy_)};
        }

        //! \return Socks version.
        socks::version socks_version() const noexcept { return ver_; }

        //! \return Contents of internal buffer.
        epee::span<const std::uint8_t> buffer() const noexcept
        {
            return {buffer_, buffer_size_};
        }

        //! \post `buffer.empty()`.
        void clear_command() noexcept { buffer_size_ = 0; }

        //! Try to set `address` as remote connection request.
        bool set_connect_command(const epee::net_utils::ipv4_network_address& address);

        //! Try to set `domain` + `port` as remote connection request.
        bool set_connect_command(boost::string_ref domain, std::uint16_t port);

        //! Try to set `address` as remote Tor hidden service connection request.
        bool set_connect_command(const net::tor_address& address);

        //! Try to set `address` as remote i2p hidden service connection request.
        bool set_connect_command(const net::i2p_address& address);

        //! Try to set `domain` as remote DNS A record lookup request.
        bool set_resolve_command(boost::string_ref domain);

        /*!
            Asynchronously connect to `proxy_address` then issue command in
            `buffer()`. The `done(...)` method will be invoked upon completion
            with `self` and potential `error`s.

            \note Must use one of the `self->set_*_command` calls before using
                this function.

            \param self ownership of object is given to function.
            \param proxy_address of the socks server.
            \return False if `self->buffer().empty()` (no command set).
         */
        static bool connect_and_send(std::shared_ptr<client> self, const stream_type::endpoint& proxy_address);

        /*!
            Assume existing connection to proxy server; asynchronously issue
            command in `buffer()`. The `done(...)` method will be invoked
            upon completion with `self` and potential `error`s.

            \note Must use one of the `self->set_*_command` calls before using
                the function.

            \param self ownership of object is given to function.
            \return False if `self->buffer().empty()` (no command set).
        */
        static bool send(std::shared_ptr<client> self);
    };

    template<typename Handler>
    class connect_client : public client
    {
        Handler handler_;

        virtual void done(boost::system::error_code error, std::shared_ptr<client>) override
        {
            handler_(error, take_socket());
        }

    public:
        explicit connect_client(stream_type::socket&& proxy, socks::version ver, Handler&& handler)
          : client(std::move(proxy), ver), handler_(std::move(handler))
        {}

        virtual ~connect_client() override {}
    };

    template<typename Handler>
    inline std::shared_ptr<client>
    make_connect_client(client::stream_type::socket&& proxy, socks::version ver, Handler handler)
    {
        return std::make_shared<connect_client<Handler>>(std::move(proxy), ver, std::move(handler));
    }
} // socks
} // net

namespace boost
{
namespace system
{
    template<>
    struct is_error_code_enum<net::socks::error>
      : true_type
    {};
} // system
} // boost