aboutsummaryrefslogtreecommitdiff
path: root/src/net
diff options
context:
space:
mode:
authorLee Clagett <code@leeclagett.com>2018-12-16 17:57:44 +0000
committerLee Clagett <code@leeclagett.com>2019-01-28 23:56:33 +0000
commit973403bc9f54ab0722b67a3c76ab6e7bafbfeedc (patch)
tree01f74938dc99a56c5d20840baa9bce66142847ae /src/net
parentMerge pull request #5062 (diff)
downloadmonero-973403bc9f54ab0722b67a3c76ab6e7bafbfeedc.tar.xz
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).
Diffstat (limited to 'src/net')
-rw-r--r--src/net/CMakeLists.txt34
-rw-r--r--src/net/error.cpp92
-rw-r--r--src/net/error.h64
-rw-r--r--src/net/fwd.h45
-rw-r--r--src/net/parse.cpp60
-rw-r--r--src/net/parse.h54
-rw-r--r--src/net/socks.cpp308
-rw-r--r--src/net/socks.h225
-rw-r--r--src/net/tor_address.cpp203
-rw-r--r--src/net/tor_address.h140
10 files changed, 1225 insertions, 0 deletions
diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt
new file mode 100644
index 000000000..a81372125
--- /dev/null
+++ b/src/net/CMakeLists.txt
@@ -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})
+
diff --git a/src/net/error.cpp b/src/net/error.cpp
new file mode 100644
index 000000000..037f44d52
--- /dev/null
+++ b/src/net/error.cpp
@@ -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;
+ }
+}
diff --git a/src/net/error.h b/src/net/error.h
new file mode 100644
index 000000000..c8338f7e2
--- /dev/null
+++ b/src/net/error.h
@@ -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
+ {};
+}
diff --git a/src/net/fwd.h b/src/net/fwd.h
new file mode 100644
index 000000000..ee7c539b0
--- /dev/null
+++ b/src/net/fwd.h
@@ -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;
+ }
+}
diff --git a/src/net/parse.cpp b/src/net/parse.cpp
new file mode 100644
index 000000000..ebf91eeff
--- /dev/null
+++ b/src/net/parse.cpp
@@ -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);
+ }
+}
diff --git a/src/net/parse.h b/src/net/parse.h
new file mode 100644
index 000000000..9195ddc2b
--- /dev/null
+++ b/src/net/parse.h
@@ -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);
+}
+
diff --git a/src/net/socks.cpp b/src/net/socks.cpp
new file mode 100644
index 000000000..f31efc8c1
--- /dev/null
+++ b/src/net/socks.cpp
@@ -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
diff --git a/src/net/socks.h b/src/net/socks.h
new file mode 100644
index 000000000..d29a51ccb
--- /dev/null
+++ b/src/net/socks.h
@@ -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
diff --git a/src/net/tor_address.cpp b/src/net/tor_address.cpp
new file mode 100644
index 000000000..904a9a0fc
--- /dev/null
+++ b/src/net/tor_address.cpp
@@ -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;
+ }
+}
diff --git a/src/net/tor_address.h b/src/net/tor_address.h
new file mode 100644
index 000000000..22d8cc119
--- /dev/null
+++ b/src/net/tor_address.h
@@ -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