Adding initial support for broadcasting transactions over Tor

- Support for ".onion" in --add-exclusive-node and --add-peer
  - Add --anonymizing-proxy for outbound Tor connections
  - Add --anonymous-inbounds for inbound Tor connections
  - Support for sharing ".onion" addresses over Tor connections
  - Support for broadcasting transactions received over RPC exclusively
    over Tor (else broadcast over public IP when Tor not enabled).
This commit is contained in:
Lee Clagett 2018-12-16 17:57:44 +00:00
parent 1e5cd3b35a
commit 973403bc9f
39 changed files with 4298 additions and 831 deletions

34
src/net/CMakeLists.txt Normal file
View file

@ -0,0 +1,34 @@
# 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.
set(net_sources error.cpp parse.cpp socks.cpp tor_address.cpp)
set(net_headers error.h parse.h socks.h tor_address.h)
monero_add_library(net ${net_sources} ${net_headers})
target_link_libraries(net epee ${Boost_ASIO_LIBRARY})

92
src/net/error.cpp Normal file
View file

@ -0,0 +1,92 @@
// 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.
#include "error.h"
#include <string>
namespace
{
struct net_category : std::error_category
{
net_category() noexcept
: std::error_category()
{}
const char* name() const noexcept override
{
return "net::error_category";
}
std::string message(int value) const override
{
switch (net::error(value))
{
case net::error::expected_tld:
return "Expected top-level domain";
case net::error::invalid_host:
return "Host value is not valid";
case net::error::invalid_i2p_address:
return "Invalid I2P address";
case net::error::invalid_port:
return "Invalid port value (expected 0-65535)";
case net::error::invalid_tor_address:
return "Invalid Tor address";
case net::error::unsupported_address:
return "Network address not supported";
default:
break;
}
return "Unknown net::error";
}
std::error_condition default_error_condition(int value) const noexcept override
{
switch (net::error(value))
{
case net::error::invalid_port:
return std::errc::result_out_of_range;
case net::error::expected_tld:
case net::error::invalid_tor_address:
default:
break;
}
return std::error_condition{value, *this};
}
};
} // anonymous
namespace net
{
std::error_category const& error_category() noexcept
{
static const net_category instance{};
return instance;
}
}

64
src/net/error.h Normal file
View file

@ -0,0 +1,64 @@
// 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 <system_error>
#include <type_traits>
namespace net
{
//! General net errors
enum class error : int
{
// 0 reserved for success (as per expect<T>)
expected_tld = 1, //!< Expected a tld
invalid_host, //!< Hostname is not valid
invalid_i2p_address,
invalid_port, //!< Outside of 0-65535 range
invalid_tor_address,//!< Invalid base32 or length
unsupported_address //!< Type not supported by `get_network_address`
};
//! \return `std::error_category` for `net` namespace.
std::error_category const& error_category() noexcept;
//! \return `net::error` as a `std::error_code` value.
inline std::error_code make_error_code(error value) noexcept
{
return std::error_code{int(value), error_category()};
}
}
namespace std
{
template<>
struct is_error_code_enum<::net::error>
: true_type
{};
}

45
src/net/fwd.h Normal file
View file

@ -0,0 +1,45 @@
// 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>
namespace net
{
enum class error : int;
class tor_address;
namespace socks
{
class client;
template<typename> class connect_handler;
enum class error : int;
enum class version : std::uint8_t;
}
}

60
src/net/parse.cpp Normal file
View file

@ -0,0 +1,60 @@
// 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.
#include "parse.h"
#include "net/tor_address.h"
#include "string_tools.h"
namespace net
{
expect<epee::net_utils::network_address>
get_network_address(const boost::string_ref address, const std::uint16_t default_port)
{
const boost::string_ref host = address.substr(0, address.rfind(':'));
if (host.empty())
return make_error_code(net::error::invalid_host);
if (host.ends_with(".onion"))
return tor_address::make(address, default_port);
if (host.ends_with(".i2p"))
return make_error_code(net::error::invalid_i2p_address); // not yet implemented (prevent public DNS lookup)
std::uint16_t port = default_port;
if (host.size() < address.size())
{
if (!epee::string_tools::get_xtype_from_string(port, std::string{address.substr(host.size() + 1)}))
return make_error_code(net::error::invalid_port);
}
std::uint32_t ip = 0;
if (epee::string_tools::get_ip_int32_from_string(ip, std::string{host}))
return {epee::net_utils::ipv4_network_address{ip, port}};
return make_error_code(net::error::unsupported_address);
}
}

54
src/net/parse.h Normal file
View file

@ -0,0 +1,54 @@
// 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 <boost/utility/string_ref.hpp>
#include <cstdint>
#include "common/expect.h"
#include "net/net_utils_base.h"
namespace net
{
/*!
Identifies onion and IPv4 addresses and returns them as a generic
`network_address`. If the type is unsupported, it might be a hostname,
and `error() == net::error::kUnsupportedAddress` is returned.
\param address An onion address, ipv4 address or hostname. Hostname
will return an error.
\param default_port If `address` does not specify a port, this value
will be used.
\return A tor or IPv4 address, else error.
*/
expect<epee::net_utils::network_address>
get_network_address(boost::string_ref address, std::uint16_t default_port);
}

308
src/net/socks.cpp Normal file
View file

@ -0,0 +1,308 @@
// 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.
#include "socks.h"
#include <algorithm>
#include <boost/asio/buffer.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <boost/endian/arithmetic.hpp>
#include <boost/endian/conversion.hpp>
#include <cstring>
#include <limits>
#include <string>
#include "net/net_utils_base.h"
#include "net/tor_address.h"
namespace net
{
namespace socks
{
namespace
{
constexpr const unsigned v4_reply_size = 8;
constexpr const std::uint8_t v4_connect_command = 1;
constexpr const std::uint8_t v4tor_resolve_command = 0xf0;
constexpr const std::uint8_t v4_request_granted = 90;
struct v4_header
{
std::uint8_t version;
std::uint8_t command_code;
boost::endian::big_uint16_t port;
boost::endian::big_uint32_t ip;
};
std::size_t write_domain_header(epee::span<std::uint8_t> out, const std::uint8_t command, const std::uint16_t port, const boost::string_ref domain)
{
if (std::numeric_limits<std::size_t>::max() - sizeof(v4_header) - 2 < domain.size())
return 0;
const std::size_t buf_size = sizeof(v4_header) + domain.size() + 2;
if (out.size() < buf_size)
return 0;
// version 4, 1 indicates invalid ip for domain extension
const v4_header temp{4, command, port, std::uint32_t(1)};
std::memcpy(out.data(), std::addressof(temp), sizeof(temp));
out.remove_prefix(sizeof(temp));
*(out.data()) = 0;
out.remove_prefix(1);
std::memcpy(out.data(), domain.data(), domain.size());
out.remove_prefix(domain.size());
*(out.data()) = 0;
return buf_size;
}
struct socks_category : boost::system::error_category
{
explicit socks_category() noexcept
: boost::system::error_category()
{}
const char* name() const noexcept override
{
return "net::socks::error_category";
}
virtual std::string message(int value) const override
{
switch (socks::error(value))
{
case socks::error::rejected:
return "Socks request rejected or failed";
case socks::error::identd_connection:
return "Socks request rejected because server cannot connect to identd on the client";
case socks::error::identd_user:
return "Socks request rejected because the client program and identd report different user-ids";
case socks::error::bad_read:
return "Socks boost::async_read read fewer bytes than expected";
case socks::error::bad_write:
return "Socks boost::async_write wrote fewer bytes than expected";
case socks::error::unexpected_version:
return "Socks server returned unexpected version in reply";
default:
break;
}
return "Unknown net::socks::error";
}
boost::system::error_condition default_error_condition(int value) const noexcept override
{
switch (socks::error(value))
{
case socks::error::bad_read:
case socks::error::bad_write:
return boost::system::errc::io_error;
case socks::error::unexpected_version:
return boost::system::errc::protocol_error;
default:
break;
};
if (1 <= value && value <= 256)
return boost::system::errc::protocol_error;
return boost::system::error_condition{value, *this};
}
};
}
const boost::system::error_category& error_category() noexcept
{
static const socks_category instance{};
return instance;
}
struct client::completed
{
std::shared_ptr<client> self_;
void operator()(const boost::system::error_code error, const std::size_t bytes) const
{
static_assert(1 < sizeof(self_->buffer_), "buffer too small for v4 response");
if (self_)
{
client& self = *self_;
self.buffer_size_ = std::min(bytes, sizeof(self.buffer_));
if (error)
self.done(error, std::move(self_));
else if (self.buffer().size() < sizeof(v4_header))
self.done(socks::error::bad_read, std::move(self_));
else if (self.buffer_[0] != 0) // response version
self.done(socks::error::unexpected_version, std::move(self_));
else if (self.buffer_[1] != v4_request_granted)
self.done(socks::error(int(self.buffer_[1]) + 1), std::move(self_));
else
self.done(boost::system::error_code{}, std::move(self_));
}
}
};
struct client::read
{
std::shared_ptr<client> self_;
static boost::asio::mutable_buffers_1 get_buffer(client& self) noexcept
{
static_assert(sizeof(v4_header) <= sizeof(self.buffer_), "buffer too small for v4 response");
return boost::asio::buffer(self.buffer_, sizeof(v4_header));
}
void operator()(const boost::system::error_code error, const std::size_t bytes)
{
if (self_)
{
client& self = *self_;
if (error)
self.done(error, std::move(self_));
else if (bytes < self.buffer().size())
self.done(socks::error::bad_write, std::move(self_));
else
boost::asio::async_read(self.proxy_, get_buffer(self), completed{std::move(self_)});
}
}
};
struct client::write
{
std::shared_ptr<client> self_;
static boost::asio::const_buffers_1 get_buffer(client const& self) noexcept
{
return boost::asio::buffer(self.buffer_, self.buffer_size_);
}
void operator()(const boost::system::error_code error)
{
if (self_)
{
client& self = *self_;
if (error)
self.done(error, std::move(self_));
else
boost::asio::async_write(self.proxy_, get_buffer(self), read{std::move(self_)});
}
}
};
client::client(stream_type::socket&& proxy, socks::version ver)
: proxy_(std::move(proxy)), buffer_size_(0), buffer_(), ver_(ver)
{}
client::~client() {}
bool client::set_connect_command(const epee::net_utils::ipv4_network_address& address)
{
switch (socks_version())
{
case version::v4:
case version::v4a:
case version::v4a_tor:
break;
default:
return false;
}
static_assert(sizeof(v4_header) < sizeof(buffer_), "buffer size too small for request");
static_assert(0 < sizeof(buffer_), "buffer size too small for null termination");
// version 4
const v4_header temp{4, v4_connect_command, address.port(), boost::endian::big_to_native(address.ip())};
std::memcpy(std::addressof(buffer_), std::addressof(temp), sizeof(temp));
buffer_[sizeof(temp)] = 0;
buffer_size_ = sizeof(temp) + 1;
return true;
}
bool client::set_connect_command(const boost::string_ref domain, std::uint16_t port)
{
switch (socks_version())
{
case version::v4a:
case version::v4a_tor:
break;
default:
return false;
}
const std::size_t buf_used = write_domain_header(buffer_, v4_connect_command, port, domain);
buffer_size_ = buf_used;
return buf_used != 0;
}
bool client::set_connect_command(const net::tor_address& address)
{
if (!address.is_unknown())
return set_connect_command(address.host_str(), address.port());
return false;
}
bool client::set_resolve_command(boost::string_ref domain)
{
if (socks_version() != version::v4a_tor)
return false;
const std::size_t buf_used = write_domain_header(buffer_, v4tor_resolve_command, 0, domain);
buffer_size_ = buf_used;
return buf_used != 0;
}
bool client::connect_and_send(std::shared_ptr<client> self, const stream_type::endpoint& proxy_address)
{
if (self && !self->buffer().empty())
{
client& alias = *self;
alias.proxy_.async_connect(proxy_address, write{std::move(self)});
return true;
}
return false;
}
bool client::send(std::shared_ptr<client> self)
{
if (self && !self->buffer().empty())
{
client& alias = *self;
boost::asio::async_write(alias.proxy_, write::get_buffer(alias), read{std::move(self)});
return true;
}
return false;
}
} // socks
} // net

225
src/net/socks.h Normal file
View file

@ -0,0 +1,225 @@
// 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 `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

203
src/net/tor_address.cpp Normal file
View file

@ -0,0 +1,203 @@
// 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.
#include "tor_address.h"
#include <algorithm>
#include <boost/spirit/include/karma_generate.hpp>
#include <boost/spirit/include/karma_uint.hpp>
#include <cassert>
#include <cstring>
#include <limits>
#include "net/error.h"
#include "serialization/keyvalue_serialization.h"
#include "storages/portable_storage.h"
#include "string_tools.h"
namespace net
{
namespace
{
constexpr const char tld[] = u8".onion";
constexpr const char unknown_host[] = "<unknown tor host>";
constexpr const unsigned v2_length = 16;
constexpr const unsigned v3_length = 56;
constexpr const char base32_alphabet[] =
u8"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz234567";
expect<void> host_check(boost::string_ref host) noexcept
{
if (!host.ends_with(tld))
return {net::error::expected_tld};
host.remove_suffix(sizeof(tld) - 1);
//! \TODO v3 has checksum, base32 decoding is required to verify it
if (host.size() != v2_length && host.size() != v3_length)
return {net::error::invalid_tor_address};
if (host.find_first_not_of(base32_alphabet) != boost::string_ref::npos)
return {net::error::invalid_tor_address};
return success();
}
struct tor_serialized
{
std::string host;
std::uint16_t port;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(host)
KV_SERIALIZE(port)
END_KV_SERIALIZE_MAP()
};
}
tor_address::tor_address(const boost::string_ref host, const std::uint16_t port) noexcept
: port_(port)
{
// this is a private constructor, throw if moved to public
assert(host.size() < sizeof(host_));
const std::size_t length = std::min(sizeof(host_) - 1, host.size());
std::memcpy(host_, host.data(), length);
std::memset(host_ + length, 0, sizeof(host_) - length);
}
const char* tor_address::unknown_str() noexcept
{
return unknown_host;
}
tor_address::tor_address() noexcept
: port_(0)
{
static_assert(sizeof(unknown_host) <= sizeof(host_), "bad buffer size");
std::memcpy(host_, unknown_host, sizeof(unknown_host));
std::memset(host_ + sizeof(unknown_host), 0, sizeof(host_) - sizeof(unknown_host));
}
expect<tor_address> tor_address::make(const boost::string_ref address, const std::uint16_t default_port)
{
boost::string_ref host = address.substr(0, address.rfind(':'));
const boost::string_ref port =
address.substr(host.size() + (host.size() == address.size() ? 0 : 1));
MONERO_CHECK(host_check(host));
std::uint16_t porti = default_port;
if (!port.empty() && !epee::string_tools::get_xtype_from_string(porti, std::string{port}))
return {net::error::invalid_port};
static_assert(v2_length <= v3_length, "bad internal host size");
static_assert(v3_length + sizeof(tld) == sizeof(tor_address::host_), "bad internal host size");
return tor_address{host, porti};
}
bool tor_address::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent)
{
tor_serialized in{};
if (in._load(src, hparent) && in.host.size() < sizeof(host_) && (in.host == unknown_host || !host_check(in.host).has_error()))
{
std::memcpy(host_, in.host.data(), in.host.size());
std::memset(host_ + in.host.size(), 0, sizeof(host_) - in.host.size());
port_ = in.port;
return true;
}
static_assert(sizeof(unknown_host) <= sizeof(host_), "bad buffer size");
std::memcpy(host_, unknown_host, sizeof(unknown_host)); // include null terminator
port_ = 0;
return false;
}
bool tor_address::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const
{
const tor_serialized out{std::string{host_}, port_};
return out.store(dest, hparent);
}
tor_address::tor_address(const tor_address& rhs) noexcept
: port_(rhs.port_)
{
std::memcpy(host_, rhs.host_, sizeof(host_));
}
tor_address& tor_address::operator=(const tor_address& rhs) noexcept
{
if (this != std::addressof(rhs))
{
port_ = rhs.port_;
std::memcpy(host_, rhs.host_, sizeof(host_));
}
return *this;
}
bool tor_address::is_unknown() const noexcept
{
static_assert(1 <= sizeof(host_), "host size too small");
return host_[0] == '<'; // character is not allowed otherwise
}
bool tor_address::equal(const tor_address& rhs) const noexcept
{
return port_ == rhs.port_ && is_same_host(rhs);
}
bool tor_address::less(const tor_address& rhs) const noexcept
{
return std::strcmp(host_str(), rhs.host_str()) < 0 || port() < rhs.port();
}
bool tor_address::is_same_host(const tor_address& rhs) const noexcept
{
//! \TODO v2 and v3 should be comparable - requires base32
return std::strcmp(host_str(), rhs.host_str()) == 0;
}
std::string tor_address::str() const
{
const std::size_t host_length = std::strlen(host_str());
const std::size_t port_length =
port_ == 0 ? 0 : std::numeric_limits<std::uint16_t>::digits10 + 2;
std::string out{};
out.reserve(host_length + port_length);
out.assign(host_str(), host_length);
if (port_ != 0)
{
out.push_back(':');
namespace karma = boost::spirit::karma;
karma::generate(std::back_inserter(out), karma::ushort_, port());
}
return out;
}
}

140
src/net/tor_address.h Normal file
View file

@ -0,0 +1,140 @@
// 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 <boost/utility/string_ref.hpp>
#include <cstdint>
#include <string>
#include "common/expect.h"
#include "net/enums.h"
#include "net/error.h"
namespace epee
{
namespace serialization
{
class portable_storage;
struct section;
}
}
namespace net
{
//! Tor onion address; internal format not condensed/decoded.
class tor_address
{
std::uint16_t port_;
char host_[63]; // null-terminated
//! Keep in private, `host.size()` has no runtime check
tor_address(boost::string_ref host, std::uint16_t port) noexcept;
public:
//! \return Size of internal buffer for host.
static constexpr std::size_t buffer_size() noexcept { return sizeof(host_); }
//! \return `<unknown tor host>`.
static const char* unknown_str() noexcept;
//! An object with `port() == 0` and `host_str() == unknown_str()`.
tor_address() noexcept;
//! \return A default constructed `tor_address` object.
static tor_address unknown() noexcept { return tor_address{}; }
/*!
Parse `address` in onion v2 or v3 format with (i.e. x.onion:80)
with `default_port` being used iff port is not specified in
`address`.
*/
static expect<tor_address> make(boost::string_ref address, std::uint16_t default_port = 0);
//! Load from epee p2p format, and \return false if not valid tor address
bool _load(epee::serialization::portable_storage& src, epee::serialization::section* hparent);
//! Store in epee p2p format
bool store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const;
// Moves and copies are currently identical
tor_address(const tor_address& rhs) noexcept;
~tor_address() = default;
tor_address& operator=(const tor_address& rhs) noexcept;
//! \return True if default constructed or via `unknown()`.
bool is_unknown() const noexcept;
bool equal(const tor_address& rhs) const noexcept;
bool less(const tor_address& rhs) const noexcept;
//! \return True if onion addresses are identical.
bool is_same_host(const tor_address& rhs) const noexcept;
//! \return `x.onion` or `x.onion:z` if `port() != 0`.
std::string str() const;
//! \return Null-terminated `x.onion` value or `unknown_str()`.
const char* host_str() const noexcept { return host_; }
//! \return Port value or `0` if unspecified.
std::uint16_t port() const noexcept { return port_; }
static constexpr bool is_loopback() noexcept { return false; }
static constexpr bool is_local() noexcept { return false; }
static constexpr epee::net_utils::address_type get_type_id() noexcept
{
return epee::net_utils::address_type::tor;
}
static constexpr epee::net_utils::zone get_zone() noexcept
{
return epee::net_utils::zone::tor;
}
//! \return `!is_unknown()`.
bool is_blockable() const noexcept { return !is_unknown(); }
};
inline bool operator==(const tor_address& lhs, const tor_address& rhs) noexcept
{
return lhs.equal(rhs);
}
inline bool operator!=(const tor_address& lhs, const tor_address& rhs) noexcept
{
return !lhs.equal(rhs);
}
inline bool operator<(const tor_address& lhs, const tor_address& rhs) noexcept
{
return lhs.less(rhs);
}
} // net