Added socks proxy (tor/i2pd/kovri) support to wallet

This commit is contained in:
Lee Clagett 2019-01-23 21:37:43 +00:00
parent e4b049da05
commit 7acfa9f3cc
21 changed files with 505 additions and 98 deletions

View File

@ -19,6 +19,11 @@ network. The transaction will not be broadcast unless an anonymity connection
is made or until `monerod` is shutdown and restarted with only public is made or until `monerod` is shutdown and restarted with only public
connections enabled. connections enabled.
Anonymity networks can also be used with `monero-wallet-cli` and
`monero-wallet-rpc` - the wallets will connect to a daemon through a proxy. The
daemon must provide a hidden service for the RPC itself, which is separate from
the hidden service for P2P connections.
## P2P Commands ## P2P Commands
@ -74,6 +79,35 @@ forwarded to `monerod` localhost port 30000.
These addresses will be shared with outgoing peers, over the same network type, These addresses will be shared with outgoing peers, over the same network type,
otherwise the peer will not be notified of the peer address by the proxy. otherwise the peer will not be notified of the peer address by the proxy.
### Wallet RPC
An anonymity network can be configured to forward incoming connections to a
`monerod` RPC port - which is independent from the configuration for incoming
P2P anonymity connections. The anonymity network (Tor/i2p) is
[configured in the same manner](#configuration), except the localhost port
must be the RPC port (typically 18081 for mainnet) instead of the p2p port:
> HiddenServiceDir /var/lib/tor/data/monero
> HiddenServicePort 18081 127.0.0.1:18081
Then the wallet will be configured to use a Tor/i2p address:
> `--proxy 127.0.0.1:9050`
> `--daemon-address rveahdfho7wo4b2m.onion`
The proxy must match the address type - a Tor proxy will not work properly with
i2p addresses, etc.
i2p and onion addresses provide the information necessary to authenticate and
encrypt the connection from end-to-end. If desired, SSL can also be applied to
the connection with `--daemon-address https://rveahdfho7wo4b2m.onion` which
requires a server certificate that is signed by a "root" certificate on the
machine running the wallet. Alternatively, `--daemon-cert-file` can be used to
specify a certificate to authenticate the server.
Proxies can also be used to connect to "clearnet" (ipv4 addresses or ICANN
domains), but `--daemon-cert-file` _must_ be used for authentication and
encryption.
### Network Types ### Network Types
#### Tor & I2P #### Tor & I2P

View File

@ -58,11 +58,6 @@
#define DEFAULT_TIMEOUT_MS_REMOTE 300000 // 5 minutes #define DEFAULT_TIMEOUT_MS_REMOTE 300000 // 5 minutes
#define TIMEOUT_EXTRA_MS_PER_BYTE 0.2 #define TIMEOUT_EXTRA_MS_PER_BYTE 0.2
#if BOOST_VERSION >= 107000
#define GET_IO_SERVICE(s) ((boost::asio::io_context&)(s).get_executor().context())
#else
#define GET_IO_SERVICE(s) ((s).get_io_service())
#endif
PRAGMA_WARNING_PUSH PRAGMA_WARNING_PUSH
namespace epee namespace epee

View File

@ -327,10 +327,17 @@ namespace net_utils
m_net_client.set_ssl(m_ssl_support, m_ssl_private_key_and_certificate_path, m_ssl_allowed_certificates, m_ssl_allowed_fingerprints, m_ssl_allow_any_cert); m_net_client.set_ssl(m_ssl_support, m_ssl_private_key_and_certificate_path, m_ssl_allowed_certificates, m_ssl_allowed_fingerprints, m_ssl_allow_any_cert);
} }
template<typename F>
void set_connector(F connector)
{
CRITICAL_REGION_LOCAL(m_lock);
m_net_client.set_connector(std::move(connector));
}
bool connect(std::chrono::milliseconds timeout) bool connect(std::chrono::milliseconds timeout)
{ {
CRITICAL_REGION_LOCAL(m_lock); CRITICAL_REGION_LOCAL(m_lock);
return m_net_client.connect(m_host_buff, m_port, timeout, "0.0.0.0"); return m_net_client.connect(m_host_buff, m_port, timeout);
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
bool disconnect() bool disconnect()

View File

@ -33,12 +33,17 @@
//#include <Ws2tcpip.h> //#include <Ws2tcpip.h>
#include <string> #include <string>
#include <boost/version.hpp> #include <boost/version.hpp>
#include <boost/asio.hpp> #include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/ssl.hpp> #include <boost/asio/ssl.hpp>
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
#include <boost/thread/future.hpp>
#include <boost/lambda/bind.hpp> #include <boost/lambda/bind.hpp>
#include <boost/lambda/lambda.hpp> #include <boost/lambda/lambda.hpp>
#include <boost/interprocess/detail/atomic.hpp> #include <boost/interprocess/detail/atomic.hpp>
#include <boost/system/error_code.hpp>
#include <functional>
#include "net/net_utils_base.h" #include "net/net_utils_base.h"
#include "net/net_ssl.h" #include "net/net_ssl.h"
#include "misc_language.h" #include "misc_language.h"
@ -55,6 +60,12 @@ namespace epee
{ {
namespace net_utils namespace net_utils
{ {
struct direct_connect
{
boost::unique_future<boost::asio::ip::tcp::socket>
operator()(const std::string& addr, const std::string& port, boost::asio::steady_timer&) const;
};
class blocked_mode_client class blocked_mode_client
{ {
@ -88,28 +99,35 @@ namespace net_utils
public: public:
inline inline
blocked_mode_client():m_initialized(false), blocked_mode_client() :
m_io_service(),
m_ctx({boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}}),
m_connector(direct_connect{}),
m_ssl_socket(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service, m_ctx.context)),
m_ssl_support(epee::net_utils::ssl_support_t::e_ssl_support_autodetect),
m_initialized(true),
m_connected(false), m_connected(false),
m_deadline(m_io_service), m_deadline(m_io_service),
m_shutdowned(0), m_shutdowned(0)
m_ssl_support(epee::net_utils::ssl_support_t::e_ssl_support_autodetect),
m_ctx({boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}}),
m_ssl_socket(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service,m_ctx.context))
{ {
m_initialized = true;
// No deadline is required until the first socket operation is started. We
// set the deadline to positive infinity so that the actor takes no action
// until a specific deadline is set.
m_deadline.expires_at(std::chrono::steady_clock::time_point::max());
// Start the persistent actor that checks for deadline expiry.
check_deadline();
} }
/*! The first/second parameters are host/port respectively. The third
parameter is for setting the timeout callback - the timer is
already set by the caller, the callee only needs to set the
behavior.
Additional asynchronous operations should be queued using the
`io_service` from the timer. The implementation should assume
multi-threaded I/O processing.
If the callee cannot start an asynchronous operation, an exception
should be thrown to signal an immediate failure.
The return value is a future to a connected socket. Asynchronous
failures should use the `set_exception` method. */
using connect_func = boost::unique_future<boost::asio::ip::tcp::socket>(const std::string&, const std::string&, boost::asio::steady_timer&);
inline inline
~blocked_mode_client() ~blocked_mode_client()
{ {
@ -128,33 +146,28 @@ namespace net_utils
} }
inline inline
bool connect(const std::string& addr, int port, std::chrono::milliseconds timeout, const std::string& bind_ip = "0.0.0.0") bool connect(const std::string& addr, int port, std::chrono::milliseconds timeout)
{ {
return connect(addr, std::to_string(port), timeout, bind_ip); return connect(addr, std::to_string(port), timeout);
} }
inline inline
try_connect_result_t try_connect(const std::string& addr, const std::string& port, const boost::asio::ip::tcp::endpoint &remote_endpoint, std::chrono::milliseconds timeout, const std::string& bind_ip, epee::net_utils::ssl_support_t ssl_support) try_connect_result_t try_connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout, epee::net_utils::ssl_support_t ssl_support)
{ {
m_ssl_socket->next_layer().open(remote_endpoint.protocol());
if(bind_ip != "0.0.0.0" && bind_ip != "0" && bind_ip != "" )
{
boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::address::from_string(addr.c_str()), 0);
m_ssl_socket->next_layer().bind(local_endpoint);
}
m_deadline.expires_from_now(timeout); m_deadline.expires_from_now(timeout);
boost::unique_future<boost::asio::ip::tcp::socket> connection = m_connector(addr, port, m_deadline);
boost::system::error_code ec = boost::asio::error::would_block; for (;;)
m_ssl_socket->next_layer().async_connect(remote_endpoint, boost::lambda::var(ec) = boost::lambda::_1);
while (ec == boost::asio::error::would_block)
{ {
m_io_service.reset();
m_io_service.run_one(); m_io_service.run_one();
if (connection.is_ready())
break;
} }
if (!ec && m_ssl_socket->next_layer().is_open()) m_ssl_socket->next_layer() = connection.get();
m_deadline.cancel();
if (m_ssl_socket->next_layer().is_open())
{ {
m_connected = true; m_connected = true;
m_deadline.expires_at(std::chrono::steady_clock::time_point::max()); m_deadline.expires_at(std::chrono::steady_clock::time_point::max());
@ -183,14 +196,14 @@ namespace net_utils
return CONNECT_SUCCESS; return CONNECT_SUCCESS;
}else }else
{ {
MWARNING("Some problems at connect, message: " << ec.message()); MWARNING("Some problems at connect, expected open socket");
return CONNECT_FAILURE; return CONNECT_FAILURE;
} }
} }
inline inline
bool connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout, const std::string& bind_ip = "0.0.0.0") bool connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout)
{ {
m_connected = false; m_connected = false;
try try
@ -205,25 +218,7 @@ namespace net_utils
// Get a list of endpoints corresponding to the server name. // Get a list of endpoints corresponding to the server name.
////////////////////////////////////////////////////////////////////////// try_connect_result_t try_connect_result = try_connect(addr, port, timeout, m_ssl_support);
boost::asio::ip::tcp::resolver resolver(m_io_service);
boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), addr, port, boost::asio::ip::tcp::resolver::query::canonical_name);
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ip::tcp::resolver::iterator end;
if(iterator == end)
{
LOG_ERROR("Failed to resolve " << addr);
return false;
}
//////////////////////////////////////////////////////////////////////////
//boost::asio::ip::tcp::endpoint remote_endpoint(boost::asio::ip::address::from_string(addr.c_str()), port);
boost::asio::ip::tcp::endpoint remote_endpoint(*iterator);
try_connect_result_t try_connect_result = try_connect(addr, port, remote_endpoint, timeout, bind_ip, m_ssl_support);
if (try_connect_result == CONNECT_FAILURE) if (try_connect_result == CONNECT_FAILURE)
return false; return false;
if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect)
@ -233,7 +228,7 @@ namespace net_utils
{ {
MERROR("SSL handshake failed on an autodetect connection, reconnecting without SSL"); MERROR("SSL handshake failed on an autodetect connection, reconnecting without SSL");
m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_disabled; m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_disabled;
if (try_connect(addr, port, remote_endpoint, timeout, bind_ip, m_ssl_support) != CONNECT_SUCCESS) if (try_connect(addr, port, timeout, m_ssl_support) != CONNECT_SUCCESS)
return false; return false;
} }
} }
@ -251,6 +246,11 @@ namespace net_utils
return true; return true;
} }
//! Change the connection routine (proxy, etc.)
void set_connector(std::function<connect_func> connector)
{
m_connector = std::move(connector);
}
inline inline
bool disconnect() bool disconnect()
@ -265,7 +265,6 @@ namespace net_utils
m_ssl_socket->next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both); m_ssl_socket->next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both);
} }
} }
catch(const boost::system::system_error& /*er*/) catch(const boost::system::system_error& /*er*/)
{ {
//LOG_ERROR("Some problems at disconnect, message: " << er.what()); //LOG_ERROR("Some problems at disconnect, message: " << er.what());
@ -304,6 +303,7 @@ namespace net_utils
// Block until the asynchronous operation has completed. // Block until the asynchronous operation has completed.
while (ec == boost::asio::error::would_block) while (ec == boost::asio::error::would_block)
{ {
m_io_service.reset();
m_io_service.run_one(); m_io_service.run_one();
} }
@ -433,6 +433,7 @@ namespace net_utils
// Block until the asynchronous operation has completed. // Block until the asynchronous operation has completed.
while (ec == boost::asio::error::would_block && !boost::interprocess::ipcdetail::atomic_read32(&m_shutdowned)) while (ec == boost::asio::error::would_block && !boost::interprocess::ipcdetail::atomic_read32(&m_shutdowned))
{ {
m_io_service.reset();
m_io_service.run_one(); m_io_service.run_one();
} }
@ -573,10 +574,6 @@ namespace net_utils
return true; return true;
} }
void set_connected(bool connected)
{
m_connected = connected;
}
boost::asio::io_service& get_io_service() boost::asio::io_service& get_io_service()
{ {
return m_io_service; return m_io_service;
@ -619,6 +616,7 @@ namespace net_utils
m_ssl_socket->async_shutdown(boost::lambda::var(ec) = boost::lambda::_1); m_ssl_socket->async_shutdown(boost::lambda::var(ec) = boost::lambda::_1);
while (ec == boost::asio::error::would_block) while (ec == boost::asio::error::would_block)
{ {
m_io_service.reset();
m_io_service.run_one(); m_io_service.run_one();
} }
// Ignore "short read" error // Ignore "short read" error
@ -665,11 +663,8 @@ namespace net_utils
boost::asio::io_service m_io_service; boost::asio::io_service m_io_service;
epee::net_utils::ssl_context_t m_ctx; epee::net_utils::ssl_context_t m_ctx;
std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> m_ssl_socket; std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> m_ssl_socket;
std::function<connect_func> m_connector;
epee::net_utils::ssl_support_t m_ssl_support; epee::net_utils::ssl_support_t m_ssl_support;
std::string m_ssl_private_key;
std::string m_ssl_certificate;
std::list<std::string> m_ssl_allowed_certificates;
bool m_ssl_allow_any_cerl;
bool m_initialized; bool m_initialized;
bool m_connected; bool m_connected;
boost::asio::steady_timer m_deadline; boost::asio::steady_timer m_deadline;
@ -790,3 +785,4 @@ namespace net_utils
}; };
} }
} }

View File

@ -44,6 +44,12 @@
#define MAKE_IP( a1, a2, a3, a4 ) (a1|(a2<<8)|(a3<<16)|(a4<<24)) #define MAKE_IP( a1, a2, a3, a4 ) (a1|(a2<<8)|(a3<<16)|(a4<<24))
#endif #endif
#if BOOST_VERSION >= 107000
#define GET_IO_SERVICE(s) ((boost::asio::io_context&)(s).get_executor().context())
#else
#define GET_IO_SERVICE(s) ((s).get_io_service())
#endif
namespace net namespace net
{ {
class tor_address; class tor_address;

View File

@ -26,8 +26,9 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # 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. # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_utils_base.cpp string_tools.cpp wipeable_string.cpp memwipe.c add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_helper.cpp net_utils_base.cpp string_tools.cpp wipeable_string.cpp memwipe.c
connection_basic.cpp network_throttle.cpp network_throttle-detail.cpp mlocker.cpp buffer.cpp net_ssl.cpp) connection_basic.cpp network_throttle.cpp network_throttle-detail.cpp mlocker.cpp buffer.cpp net_ssl.cpp)
if (USE_READLINE AND GNU_READLINE_FOUND) if (USE_READLINE AND GNU_READLINE_FOUND)
add_library(epee_readline STATIC readline_buffer.cpp) add_library(epee_readline STATIC readline_buffer.cpp)
endif() endif()

View File

@ -0,0 +1,54 @@
#include "net/net_helper.h"
namespace epee
{
namespace net_utils
{
boost::unique_future<boost::asio::ip::tcp::socket>
direct_connect::operator()(const std::string& addr, const std::string& port, boost::asio::steady_timer& timeout) const
{
// Get a list of endpoints corresponding to the server name.
//////////////////////////////////////////////////////////////////////////
boost::asio::ip::tcp::resolver resolver(GET_IO_SERVICE(timeout));
boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), addr, port, boost::asio::ip::tcp::resolver::query::canonical_name);
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ip::tcp::resolver::iterator end;
if(iterator == end) // Documentation states that successful call is guaranteed to be non-empty
throw boost::system::system_error{boost::asio::error::fault, "Failed to resolve " + addr};
//////////////////////////////////////////////////////////////////////////
struct new_connection
{
boost::promise<boost::asio::ip::tcp::socket> result_;
boost::asio::ip::tcp::socket socket_;
explicit new_connection(boost::asio::io_service& io_service)
: result_(), socket_(io_service)
{}
};
const auto shared = std::make_shared<new_connection>(GET_IO_SERVICE(timeout));
timeout.async_wait([shared] (boost::system::error_code error)
{
if (error != boost::system::errc::operation_canceled && shared && shared->socket_.is_open())
{
shared->socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
shared->socket_.close();
}
});
shared->socket_.async_connect(*iterator, [shared] (boost::system::error_code error)
{
if (shared)
{
if (error)
shared->result_.set_exception(boost::system::system_error{error});
else
shared->result_.set_value(std::move(shared->socket_));
}
});
return shared->result_.get_future();
}
}
}

View File

@ -26,8 +26,8 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # 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. # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(net_sources error.cpp parse.cpp socks.cpp tor_address.cpp i2p_address.cpp) set(net_sources error.cpp i2p_address.cpp parse.cpp socks.cpp socks_connect.cpp tor_address.cpp)
set(net_headers error.h parse.h socks.h tor_address.h i2p_address.h) set(net_headers error.h i2p_address.h parse.h socks.h socks_connect.h tor_address.h)
monero_add_library(net ${net_sources} ${net_headers}) monero_add_library(net ${net_sources} ${net_headers})
target_link_libraries(net common epee ${Boost_ASIO_LIBRARY}) target_link_libraries(net common epee ${Boost_ASIO_LIBRARY})

View File

@ -1,4 +1,4 @@
// Copyright (c) 2018, The Monero Project // Copyright (c) 2018-2019, The Monero Project
// //
// All rights reserved. // All rights reserved.
// //
@ -193,7 +193,7 @@ namespace socks
else if (bytes < self.buffer().size()) else if (bytes < self.buffer().size())
self.done(socks::error::bad_write, std::move(self_)); self.done(socks::error::bad_write, std::move(self_));
else else
boost::asio::async_read(self.proxy_, get_buffer(self), completed{std::move(self_)}); boost::asio::async_read(self.proxy_, get_buffer(self), self.strand_.wrap(completed{std::move(self_)}));
} }
} }
}; };
@ -215,13 +215,13 @@ namespace socks
if (error) if (error)
self.done(error, std::move(self_)); self.done(error, std::move(self_));
else else
boost::asio::async_write(self.proxy_, get_buffer(self), read{std::move(self_)}); boost::asio::async_write(self.proxy_, get_buffer(self), self.strand_.wrap(read{std::move(self_)}));
} }
} }
}; };
client::client(stream_type::socket&& proxy, socks::version ver) client::client(stream_type::socket&& proxy, socks::version ver)
: proxy_(std::move(proxy)), buffer_size_(0), buffer_(), ver_(ver) : proxy_(std::move(proxy)), strand_(proxy_.get_io_service()), buffer_size_(0), buffer_(), ver_(ver)
{} {}
client::~client() {} client::~client() {}
@ -296,7 +296,7 @@ namespace socks
if (self && !self->buffer().empty()) if (self && !self->buffer().empty())
{ {
client& alias = *self; client& alias = *self;
alias.proxy_.async_connect(proxy_address, write{std::move(self)}); alias.proxy_.async_connect(proxy_address, alias.strand_.wrap(write{std::move(self)}));
return true; return true;
} }
return false; return false;
@ -307,10 +307,26 @@ namespace socks
if (self && !self->buffer().empty()) if (self && !self->buffer().empty())
{ {
client& alias = *self; client& alias = *self;
boost::asio::async_write(alias.proxy_, write::get_buffer(alias), read{std::move(self)}); boost::asio::async_write(alias.proxy_, write::get_buffer(alias), alias.strand_.wrap(read{std::move(self)}));
return true; return true;
} }
return false; return false;
} }
void client::async_close::operator()(boost::system::error_code error)
{
if (self_ && error != boost::system::errc::operation_canceled)
{
const std::shared_ptr<client> self = std::move(self_);
self->strand_.dispatch([self] ()
{
if (self && self->proxy_.is_open())
{
self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
self->proxy_.close();
}
});
}
}
} // socks } // socks
} // net } // net

View File

@ -1,4 +1,4 @@
// Copyright (c) 2018, The Monero Project // Copyright (c) 2018-2019, The Monero Project
// //
// All rights reserved. // All rights reserved.
// //
@ -31,6 +31,7 @@
#include <cstdint> #include <cstdint>
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#include <boost/asio/io_service.hpp> #include <boost/asio/io_service.hpp>
#include <boost/asio/strand.hpp>
#include <boost/system/error_code.hpp> #include <boost/system/error_code.hpp>
#include <boost/type_traits/integral_constant.hpp> #include <boost/type_traits/integral_constant.hpp>
#include <boost/utility/string_ref.hpp> #include <boost/utility/string_ref.hpp>
@ -92,6 +93,7 @@ namespace socks
class client class client
{ {
boost::asio::ip::tcp::socket proxy_; boost::asio::ip::tcp::socket proxy_;
boost::asio::io_service::strand strand_;
std::uint16_t buffer_size_; std::uint16_t buffer_size_;
std::uint8_t buffer_[1024]; std::uint8_t buffer_[1024];
socks::version ver_; socks::version ver_;
@ -168,6 +170,8 @@ namespace socks
\note Must use one of the `self->set_*_command` calls before using \note Must use one of the `self->set_*_command` calls before using
this function. this function.
\note Only `async_close` can be invoked on `self` until the `done`
callback is invoked.
\param self ownership of object is given to function. \param self ownership of object is given to function.
\param proxy_address of the socks server. \param proxy_address of the socks server.
@ -182,11 +186,21 @@ namespace socks
\note Must use one of the `self->set_*_command` calls before using \note Must use one of the `self->set_*_command` calls before using
the function. the function.
\note Only `async_close` can be invoked on `self` until the `done`
callback is invoked.
\param self ownership of object is given to function. \param self ownership of object is given to function.
\return False if `self->buffer().empty()` (no command set). \return False if `self->buffer().empty()` (no command set).
*/ */
static bool send(std::shared_ptr<client> self); static bool send(std::shared_ptr<client> self);
/*! Callback for closing socket. Thread-safe with `*send` functions;
never blocks (uses strands). */
struct async_close
{
std::shared_ptr<client> self_;
void operator()(boost::system::error_code error = boost::system::error_code{});
};
}; };
template<typename Handler> template<typename Handler>

90
src/net/socks_connect.cpp Normal file
View File

@ -0,0 +1,90 @@
// Copyright (c) 2019, 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.
#include "socks_connect.h"
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
#include <cstdint>
#include <memory>
#include <system_error>
#include "net/error.h"
#include "net/net_utils_base.h"
#include "net/socks.h"
#include "string_tools.h"
namespace net
{
namespace socks
{
boost::unique_future<boost::asio::ip::tcp::socket>
connector::operator()(const std::string& remote_host, const std::string& remote_port, boost::asio::steady_timer& timeout) const
{
struct future_socket
{
boost::promise<boost::asio::ip::tcp::socket> result_;
void operator()(boost::system::error_code error, boost::asio::ip::tcp::socket&& socket)
{
if (error)
result_.set_exception(boost::system::system_error{error});
else
result_.set_value(std::move(socket));
}
};
boost::unique_future<boost::asio::ip::tcp::socket> out{};
{
std::uint16_t port = 0;
if (!epee::string_tools::get_xtype_from_string(port, remote_port))
throw std::system_error{net::error::invalid_port, "Remote port for socks proxy"};
bool is_set = false;
std::uint32_t ip_address = 0;
boost::promise<boost::asio::ip::tcp::socket> result{};
out = result.get_future();
const auto proxy = net::socks::make_connect_client(
boost::asio::ip::tcp::socket{GET_IO_SERVICE(timeout)}, net::socks::version::v4a, future_socket{std::move(result)}
);
if (epee::string_tools::get_ip_int32_from_string(ip_address, remote_host))
is_set = proxy->set_connect_command(epee::net_utils::ipv4_network_address{ip_address, port});
else
is_set = proxy->set_connect_command(remote_host, port);
if (!is_set || !net::socks::client::connect_and_send(proxy, proxy_address))
throw std::system_error{net::error::invalid_host, "Address for socks proxy"};
timeout.async_wait(net::socks::client::async_close{std::move(proxy)});
}
return out;
}
} // socks
} // net

55
src/net/socks_connect.h Normal file
View File

@ -0,0 +1,55 @@
// Copyright (c) 2019, 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 <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/thread/future.hpp>
#include <string>
namespace net
{
namespace socks
{
//! Primarily for use with `epee::net_utils::http_client`.
struct connector
{
boost::asio::ip::tcp::endpoint proxy_address;
/*! Creates a new socket, asynchronously connects to `proxy_address`,
and requests a connection to `remote_host` on `remote_port`. Sets
socket as closed if `timeout` is reached.
\return The socket if successful, and exception in the future with
error otherwise. */
boost::unique_future<boost::asio::ip::tcp::socket>
operator()(const std::string& remote_host, const std::string& remote_port, boost::asio::steady_timer& timeout) const;
};
} // socks
} // net

View File

@ -63,6 +63,7 @@ target_link_libraries(wallet
cryptonote_core cryptonote_core
mnemonics mnemonics
device_trezor device_trezor
net
${LMDB_LIBRARY} ${LMDB_LIBRARY}
${Boost_CHRONO_LIBRARY} ${Boost_CHRONO_LIBRARY}
${Boost_SERIALIZATION_LIBRARY} ${Boost_SERIALIZATION_LIBRARY}

View File

@ -2173,8 +2173,7 @@ void WalletImpl::pendingTxPostProcess(PendingTransactionImpl * pending)
bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl) bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl)
{ {
// claim RPC so there's no in-memory encryption for now if (!m_wallet->init(daemon_address, m_daemon_login, tcp::endpoint{}, upper_transaction_size_limit))
if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit, ssl))
return false; return false;
// in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks)

View File

@ -38,6 +38,7 @@
#include <boost/algorithm/string/trim.hpp> #include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/range/adaptor/transformed.hpp> #include <boost/range/adaptor/transformed.hpp>
#include "include_base_utils.h" #include "include_base_utils.h"
using namespace epee; using namespace epee;
@ -75,6 +76,7 @@ using namespace epee;
#include "ringdb.h" #include "ringdb.h"
#include "device/device_cold.hpp" #include "device/device_cold.hpp"
#include "device_trezor/device_trezor.hpp" #include "device_trezor/device_trezor.hpp"
#include "net/socks_connect.h"
extern "C" extern "C"
{ {
@ -231,6 +233,7 @@ namespace
struct options { struct options {
const command_line::arg_descriptor<std::string> daemon_address = {"daemon-address", tools::wallet2::tr("Use daemon instance at <host>:<port>"), ""}; const command_line::arg_descriptor<std::string> daemon_address = {"daemon-address", tools::wallet2::tr("Use daemon instance at <host>:<port>"), ""};
const command_line::arg_descriptor<std::string> daemon_host = {"daemon-host", tools::wallet2::tr("Use daemon instance at host <arg> instead of localhost"), ""}; const command_line::arg_descriptor<std::string> daemon_host = {"daemon-host", tools::wallet2::tr("Use daemon instance at host <arg> instead of localhost"), ""};
const command_line::arg_descriptor<std::string> proxy = {"proxy", tools::wallet2::tr("[<ip>:]<port> socks proxy to use for daemon connections"), {}, true};
const command_line::arg_descriptor<bool> trusted_daemon = {"trusted-daemon", tools::wallet2::tr("Enable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor<bool> trusted_daemon = {"trusted-daemon", tools::wallet2::tr("Enable commands which rely on a trusted daemon"), false};
const command_line::arg_descriptor<bool> untrusted_daemon = {"untrusted-daemon", tools::wallet2::tr("Disable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor<bool> untrusted_daemon = {"untrusted-daemon", tools::wallet2::tr("Disable commands which rely on a trusted daemon"), false};
const command_line::arg_descriptor<std::string> password = {"password", tools::wallet2::tr("Wallet password (escape/quote as needed)"), "", true}; const command_line::arg_descriptor<std::string> password = {"password", tools::wallet2::tr("Wallet password (escape/quote as needed)"), "", true};
@ -303,6 +306,8 @@ std::string get_weight_string(const cryptonote::transaction &tx, size_t blob_siz
std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
{ {
namespace ip = boost::asio::ip;
const bool testnet = command_line::get_arg(vm, opts.testnet); const bool testnet = command_line::get_arg(vm, opts.testnet);
const bool stagenet = command_line::get_arg(vm, opts.stagenet); const bool stagenet = command_line::get_arg(vm, opts.stagenet);
const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET; const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET;
@ -352,6 +357,44 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
if (daemon_address.empty()) if (daemon_address.empty())
daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port);
boost::asio::ip::tcp::endpoint proxy{};
if (command_line::has_arg(vm, opts.proxy))
{
namespace ip = boost::asio::ip;
const boost::string_ref real_daemon = boost::string_ref{daemon_address}.substr(0, daemon_address.rfind(':'));
// onion and i2p addresses contain information about the server cert
// which both authenticates and encrypts
const bool unencrypted_proxy =
!real_daemon.ends_with(".onion") && !real_daemon.ends_with(".i2p") &&
daemon_ssl_allowed_certificates.empty() && daemon_ssl_allowed_fingerprints.empty();
THROW_WALLET_EXCEPTION_IF(
unencrypted_proxy,
tools::error::wallet_internal_error,
std::string{"Use of --"} + opts.proxy.name + " requires --" + opts.daemon_ssl_allowed_certificates.name + " or --" + opts.daemon_ssl_allowed_fingerprints.name + " or use of a .onion/.i2p domain"
);
const auto proxy_address = command_line::get_arg(vm, opts.proxy);
boost::string_ref proxy_port{proxy_address};
boost::string_ref proxy_host = proxy_port.substr(0, proxy_port.rfind(":"));
if (proxy_port.size() == proxy_host.size())
proxy_host = "127.0.0.1";
else
proxy_port = proxy_port.substr(proxy_host.size() + 1);
uint16_t port_value = 0;
THROW_WALLET_EXCEPTION_IF(
!epee::string_tools::get_xtype_from_string(port_value, std::string{proxy_port}),
tools::error::wallet_internal_error,
std::string{"Invalid port specified for --"} + opts.proxy.name
);
boost::system::error_code error{};
proxy = ip::tcp::endpoint{ip::address::from_string(std::string{proxy_host}, error), port_value};
THROW_WALLET_EXCEPTION_IF(bool(error), tools::error::wallet_internal_error, std::string{"Invalid IP address specified for --"} + opts.proxy.name);
}
boost::optional<bool> trusted_daemon; boost::optional<bool> trusted_daemon;
if (!command_line::is_arg_defaulted(vm, opts.trusted_daemon) || !command_line::is_arg_defaulted(vm, opts.untrusted_daemon)) if (!command_line::is_arg_defaulted(vm, opts.trusted_daemon) || !command_line::is_arg_defaulted(vm, opts.untrusted_daemon))
trusted_daemon = command_line::get_arg(vm, opts.trusted_daemon) && !command_line::get_arg(vm, opts.untrusted_daemon); trusted_daemon = command_line::get_arg(vm, opts.trusted_daemon) && !command_line::get_arg(vm, opts.untrusted_daemon);
@ -388,8 +431,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector);
std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds, unattended)); std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds, unattended));
wallet->init(std::move(daemon_address), std::move(login), 0, *trusted_daemon, ssl_support, std::make_pair(daemon_ssl_private_key, daemon_ssl_certificate), ssl_allowed_certificates, ssl_allowed_fingerprints, daemon_ssl_allow_any_cert); wallet->init(std::move(daemon_address), std::move(login), std::move(proxy), 0, *trusted_daemon, ssl_support, std::make_pair(daemon_ssl_private_key, daemon_ssl_certificate), ssl_allowed_certificates, ssl_allowed_fingerprints, daemon_ssl_allow_any_cert);
boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir);
wallet->set_ring_database(ringdb_path.string()); wallet->set_ring_database(ringdb_path.string());
wallet->get_message_store().set_options(vm); wallet->get_message_store().set_options(vm);
@ -1046,6 +1088,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
const options opts{}; const options opts{};
command_line::add_arg(desc_params, opts.daemon_address); command_line::add_arg(desc_params, opts.daemon_address);
command_line::add_arg(desc_params, opts.daemon_host); command_line::add_arg(desc_params, opts.daemon_host);
command_line::add_arg(desc_params, opts.proxy);
command_line::add_arg(desc_params, opts.trusted_daemon); command_line::add_arg(desc_params, opts.trusted_daemon);
command_line::add_arg(desc_params, opts.untrusted_daemon); command_line::add_arg(desc_params, opts.untrusted_daemon);
command_line::add_arg(desc_params, opts.password); command_line::add_arg(desc_params, opts.password);
@ -1109,7 +1152,7 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert) bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert)
{ {
m_checkpoints.init_default_checkpoints(m_nettype); m_checkpoints.init_default_checkpoints(m_nettype);
if(m_http_client.is_connected()) if(m_http_client.is_connected())
@ -1119,6 +1162,10 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::
m_daemon_address = std::move(daemon_address); m_daemon_address = std::move(daemon_address);
m_daemon_login = std::move(daemon_login); m_daemon_login = std::move(daemon_login);
m_trusted_daemon = trusted_daemon; m_trusted_daemon = trusted_daemon;
if (proxy != boost::asio::ip::tcp::endpoint{})
m_http_client.set_connector(net::socks::connector{std::move(proxy)});
// When switching from light wallet to full wallet, we need to reset the height we got from lw node.
return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl_support, private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert); return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl_support, private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------

View File

@ -680,7 +680,9 @@ namespace tools
bool deinit(); bool deinit();
bool init(std::string daemon_address = "http://localhost:8080", bool init(std::string daemon_address = "http://localhost:8080",
boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_weight_limit = 0, boost::optional<epee::net_utils::http::login> daemon_login = boost::none,
boost::asio::ip::tcp::endpoint proxy = {},
uint64_t upper_transaction_weight_limit = 0,
bool trusted_daemon = true, bool trusted_daemon = true,
epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect,
const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::pair<std::string, std::string> &private_key_and_certificate_path = {},

View File

@ -53,7 +53,7 @@ int ColdOutputsFuzzer::init()
try try
{ {
wallet.init("", boost::none, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); wallet.init("", boost::none, boost::asio::ip::tcp::endpoint{}, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled);
wallet.set_subaddress_lookahead(1, 1); wallet.set_subaddress_lookahead(1, 1);
wallet.generate("", "", spendkey, true, false); wallet.generate("", "", spendkey, true, false);
} }

View File

@ -54,7 +54,7 @@ int ColdTransactionFuzzer::init()
try try
{ {
wallet.init("", boost::none, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); wallet.init("", boost::none, boost::asio::ip::tcp::endpoint{}, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled);
wallet.set_subaddress_lookahead(1, 1); wallet.set_subaddress_lookahead(1, 1);
wallet.generate("", "", spendkey, true, false); wallet.generate("", "", spendkey, true, false);
} }

View File

@ -54,7 +54,7 @@ int SignatureFuzzer::init()
try try
{ {
wallet.init("", boost::none, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); wallet.init("", boost::none, boost::asio::ip::tcp::endpoint{}, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled);
wallet.set_subaddress_lookahead(1, 1); wallet.set_subaddress_lookahead(1, 1);
wallet.generate("", "", spendkey, true, false); wallet.generate("", "", spendkey, true, false);

View File

@ -71,7 +71,7 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet)
try try
{ {
wallet.init("", boost::none, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); wallet.init("", boost::none, boost::asio::ip::tcp::endpoint{}, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled);
wallet.set_subaddress_lookahead(1, 1); wallet.set_subaddress_lookahead(1, 1);
wallet.generate("", "", spendkey, true, false); wallet.generate("", "", spendkey, true, false);
ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(cryptonote::TESTNET)); ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(cryptonote::TESTNET));

View File

@ -33,6 +33,7 @@
#include <boost/asio/io_service.hpp> #include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#include <boost/asio/read.hpp> #include <boost/asio/read.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp> #include <boost/asio/write.hpp>
#include <boost/endian/conversion.hpp> #include <boost/endian/conversion.hpp>
#include <boost/system/error_code.hpp> #include <boost/system/error_code.hpp>
@ -45,6 +46,7 @@
#include "net/error.h" #include "net/error.h"
#include "net/net_utils_base.h" #include "net/net_utils_base.h"
#include "net/socks.h" #include "net/socks.h"
#include "net/socks_connect.h"
#include "net/parse.h" #include "net/parse.h"
#include "net/tor_address.h" #include "net/tor_address.h"
#include "p2p/net_peerlist_boost_serialization.h" #include "p2p/net_peerlist_boost_serialization.h"
@ -742,4 +744,92 @@ TEST(socks_client, resolve_command)
while (test_client->called_ == 1); while (test_client->called_ == 1);
} }
TEST(socks_connector, host)
{
io_thread io{};
boost::asio::steady_timer timeout{io.io_service};
timeout.expires_from_now(std::chrono::seconds{5});
boost::unique_future<boost::asio::ip::tcp::socket> sock =
net::socks::connector{io.acceptor.local_endpoint()}("example.com", "8080", timeout);
while (!io.connected);
const std::uint8_t expected_bytes[] = {
4, 1, 0x1f, 0x90, 0x00, 0x00, 0x00, 0x01, 0x00,
'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', 0x00
};
std::uint8_t actual_bytes[sizeof(expected_bytes)];
boost::asio::read(io.server, boost::asio::buffer(actual_bytes));
EXPECT_TRUE(std::memcmp(expected_bytes, actual_bytes, sizeof(actual_bytes)) == 0);
const std::uint8_t reply_bytes[] = {0, 90, 0, 0, 0, 0, 0, 0};
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3}));
EXPECT_TRUE(sock.get().is_open());
}
TEST(socks_connector, ipv4)
{
io_thread io{};
boost::asio::steady_timer timeout{io.io_service};
timeout.expires_from_now(std::chrono::seconds{5});
boost::unique_future<boost::asio::ip::tcp::socket> sock =
net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout);
while (!io.connected);
const std::uint8_t expected_bytes[] = {
4, 1, 0x1f, 0x90, 0xfa, 0x58, 0x7d, 0x63, 0x00
};
std::uint8_t actual_bytes[sizeof(expected_bytes)];
boost::asio::read(io.server, boost::asio::buffer(actual_bytes));
EXPECT_TRUE(std::memcmp(expected_bytes, actual_bytes, sizeof(actual_bytes)) == 0);
const std::uint8_t reply_bytes[] = {0, 90, 0, 0, 0, 0, 0, 0};
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3}));
EXPECT_TRUE(sock.get().is_open());
}
TEST(socks_connector, error)
{
io_thread io{};
boost::asio::steady_timer timeout{io.io_service};
timeout.expires_from_now(std::chrono::seconds{5});
boost::unique_future<boost::asio::ip::tcp::socket> sock =
net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout);
while (!io.connected);
const std::uint8_t expected_bytes[] = {
4, 1, 0x1f, 0x90, 0xfa, 0x58, 0x7d, 0x63, 0x00
};
std::uint8_t actual_bytes[sizeof(expected_bytes)];
boost::asio::read(io.server, boost::asio::buffer(actual_bytes));
EXPECT_TRUE(std::memcmp(expected_bytes, actual_bytes, sizeof(actual_bytes)) == 0);
const std::uint8_t reply_bytes[] = {0, 91, 0, 0, 0, 0, 0, 0};
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3}));
EXPECT_THROW(sock.get().is_open(), boost::system::system_error);
}
TEST(socks_connector, timeout)
{
io_thread io{};
boost::asio::steady_timer timeout{io.io_service};
timeout.expires_from_now(std::chrono::milliseconds{10});
boost::unique_future<boost::asio::ip::tcp::socket> sock =
net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout);
ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3}));
EXPECT_THROW(sock.get().is_open(), boost::system::system_error);
}