- 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).pull/200/head
parent
1e5cd3b35a
commit
973403bc9f
@ -0,0 +1,179 @@
|
|||||||
|
# Anonymity Networks with Monero
|
||||||
|
|
||||||
|
Currently only Tor has been integrated into Monero. Providing support for
|
||||||
|
Kovri/I2P should be minimal, but has not yet been attempted. The usage of
|
||||||
|
these networks is still considered experimental - there are a few pessimistic
|
||||||
|
cases where privacy is leaked. The design is intended to maximize privacy of
|
||||||
|
the source of a transaction by broadcasting it over an anonymity network, while
|
||||||
|
relying on IPv4 for the remainder of messages to make surrounding node attacks
|
||||||
|
(via sybil) more difficult.
|
||||||
|
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
If _any_ anonymity network is enabled, transactions being broadcast that lack
|
||||||
|
a valid "context" (i.e. the transaction did not come from a p2p connection),
|
||||||
|
will only be sent to peers on anonymity networks. If an anonymity network is
|
||||||
|
enabled but no peers over an anonymity network are available, an error is
|
||||||
|
logged and the transaction is kept for future broadcasting over an anonymity
|
||||||
|
network. The transaction will not be broadcast unless an anonymity connection
|
||||||
|
is made or until `monerod` is shutdown and restarted with only public
|
||||||
|
connections enabled.
|
||||||
|
|
||||||
|
|
||||||
|
## P2P Commands
|
||||||
|
|
||||||
|
Only handshakes, peer timed syncs, and transaction broadcast messages are
|
||||||
|
supported over anonymity networks. If one `--add-exclusive-node` onion address
|
||||||
|
is specified, then no syncing will take place and only transaction broadcasting
|
||||||
|
can occur. It is therefore recommended that `--add-exclusive-node` be combined
|
||||||
|
with additional exclusive IPv4 address(es).
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Anonymity networks have no seed nodes (the feature is still considered
|
||||||
|
experimental), so a user must specify an address. If configured properly,
|
||||||
|
additional peers can be found through typical p2p peerlist sharing.
|
||||||
|
|
||||||
|
### Outbound Connections
|
||||||
|
|
||||||
|
Connecting to an anonymous address requires the command line option
|
||||||
|
`--proxy` which tells `monerod` the ip/port of a socks proxy provided by a
|
||||||
|
separate process. On most systems the configuration will look like:
|
||||||
|
|
||||||
|
> `--proxy tor,127.0.0.1:9050,10`
|
||||||
|
> `--proxy i2p,127.0.0.1:9000`
|
||||||
|
|
||||||
|
which tells `monerod` that ".onion" p2p addresses can be forwarded to a socks
|
||||||
|
proxy at IP 127.0.0.1 port 9050 with a max of 10 outgoing connections and
|
||||||
|
".i2p" p2p addresses can be forwarded to a socks proxy at IP 127.0.0.1 port 9000
|
||||||
|
with the default max outgoing connections. Since there are no seed nodes for
|
||||||
|
anonymity connections, peers must be manually specified:
|
||||||
|
|
||||||
|
> `--add-exclusive-node rveahdfho7wo4b2m.onion:28083`
|
||||||
|
> `--add-peer rveahdfho7wo4b2m.onion:28083`
|
||||||
|
|
||||||
|
Either option can be listed multiple times, and can specify any mix of Tor,
|
||||||
|
I2P, and IPv4 addresses. Using `--add-exclusive-node` will prevent the usage of
|
||||||
|
seed nodes on ALL networks, which will typically be undesireable.
|
||||||
|
|
||||||
|
### Inbound Connections
|
||||||
|
|
||||||
|
Receiving anonymity connections is done through the option
|
||||||
|
`--anonymous-inbound`. This option tells `monerod` the inbound address, network
|
||||||
|
type, and max connections:
|
||||||
|
|
||||||
|
> `--anonymous-inbound rveahdfho7wo4b2m.onion:28083,127.0.0.28083,25`
|
||||||
|
> `--anonymous-inbound foobar.i2p:5000,127.0.0.1:30000`
|
||||||
|
|
||||||
|
which tells `monerod` that a max of 25 inbound Tor connections are being
|
||||||
|
received at address "rveahdfho7wo4b2m.onion:28083" and forwarded to `monerod`
|
||||||
|
localhost port 28083, and a default max I2P connections are being received at
|
||||||
|
address "foobar.i2p:5000" and forwarded to `monerod` localhost port 30000.
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Network Types
|
||||||
|
|
||||||
|
#### Tor
|
||||||
|
|
||||||
|
Options `--add-exclusive-node` and `--add-peer` recognize ".onion" addresses,
|
||||||
|
and will properly forward those addresses to the proxy provided with
|
||||||
|
`--proxy tor,...`.
|
||||||
|
|
||||||
|
Option `--anonymous-inbound` also recognizes ".onion" addresses, and will
|
||||||
|
automatically be sent out to outgoing Tor connections so the peer can
|
||||||
|
distribute the address to its other peers.
|
||||||
|
|
||||||
|
##### Configuration
|
||||||
|
|
||||||
|
Tor must be configured for hidden services. An example configuration ("torrc")
|
||||||
|
might look like:
|
||||||
|
|
||||||
|
> HiddenServiceDir /var/lib/tor/data/monero
|
||||||
|
> HiddenServicePort 28083 127.0.0.1:28083
|
||||||
|
|
||||||
|
This will store key information in `/var/lib/tor/data/monero` and will forward
|
||||||
|
"Tor port" 28083 to port 28083 of ip 127.0.0.1. The file
|
||||||
|
`/usr/lib/tor/data/monero/hostname` will contain the ".onion" address for use
|
||||||
|
with `--anonymous-inbound`.
|
||||||
|
|
||||||
|
#### Kovri/I2P
|
||||||
|
|
||||||
|
Support for this network has not been implemented. Using ".i2p" addresses or
|
||||||
|
specifying "i2p" will currently generate an error.
|
||||||
|
|
||||||
|
|
||||||
|
## Privacy Limitations
|
||||||
|
|
||||||
|
There are currently some techniques that could be used to _possibly_ identify
|
||||||
|
the machine that broadcast a transaction over an anonymity network.
|
||||||
|
|
||||||
|
### Timestamps
|
||||||
|
|
||||||
|
The peer timed sync command sends the current time in the message. This value
|
||||||
|
can be used to link an onion address to an IPv4/IPv6 address. If a peer first
|
||||||
|
sees a transaction over Tor, it could _assume_ (possibly incorrectly) that the
|
||||||
|
transaction originated from the peer. If both the Tor connection and an
|
||||||
|
IPv4/IPv6 connection have timestamps that are approximately close in value they
|
||||||
|
could be used to link the two connections. This is less likely to happen if the
|
||||||
|
system clock is fairly accurate - many peers on the Monero network should have
|
||||||
|
similar timestamps.
|
||||||
|
|
||||||
|
#### Mitigation
|
||||||
|
|
||||||
|
Keep the system clock accurate so that fingerprinting is more difficult. In
|
||||||
|
the future a random offset might be applied to anonymity networks so that if
|
||||||
|
the system clock is noticeably off (and therefore more fingerprintable),
|
||||||
|
linking the public IPv4/IPv6 connections with the anonymity networks will be
|
||||||
|
more difficult.
|
||||||
|
|
||||||
|
### Bandwidth Usage
|
||||||
|
|
||||||
|
An ISP can passively monitor `monerod` connections from a node and observe when
|
||||||
|
a transaction is sent over a Tor/Kovri connection via timing analysis + size of
|
||||||
|
data sent during that timeframe. Kovri should provide better protection against
|
||||||
|
this attack - its connections are not circuit based. However, if a node is
|
||||||
|
only using Kovri for broadcasting Monero transactions, the total aggregate of
|
||||||
|
Kovri/I2P data would also leak information.
|
||||||
|
|
||||||
|
#### Mitigation
|
||||||
|
|
||||||
|
There is no current mitigation for the user right now. This attack is fairly
|
||||||
|
sophisticated, and likely requires support from the internet host of a Monero
|
||||||
|
user.
|
||||||
|
|
||||||
|
In the near future, "whitening" the amount of data sent over anonymity network
|
||||||
|
connections will be performed. An attempt will be made to make a transaction
|
||||||
|
broadcast indistinguishable from a peer timed sync command.
|
||||||
|
|
||||||
|
### Intermittent Monero Syncing
|
||||||
|
|
||||||
|
If a user only runs `monerod` to send a transaction then quit, this can also
|
||||||
|
be used by an ISP to link a user to a transaction.
|
||||||
|
|
||||||
|
#### Mitigation
|
||||||
|
|
||||||
|
Run `monerod` as often as possible to conceal when transactions are being sent.
|
||||||
|
Future versions will also have peers that first receive a transaction over an
|
||||||
|
anonymity network delay the broadcast to public peers by a randomized amount.
|
||||||
|
This will not completetely mitigate a user who syncs up sends then quits, in
|
||||||
|
part because this rule is not enforceable, so this mitigation strategy is
|
||||||
|
simply a best effort attempt.
|
||||||
|
|
||||||
|
### Active Bandwidth Shaping
|
||||||
|
|
||||||
|
An attacker could attempt to bandwidth shape traffic in an attempt to determine
|
||||||
|
the source of a Tor/Kovri/I2P connection. There isn't great mitigation against
|
||||||
|
this, but Kovri/I2P should provide better protection against this attack since
|
||||||
|
the connections are not circuit based.
|
||||||
|
|
||||||
|
#### Mitigation
|
||||||
|
|
||||||
|
The best mitigiation is to use Kovri/I2P instead of Tor. However, Kovri/I2P
|
||||||
|
has a smaller set of users (less cover traffic) and academic reviews, so there
|
||||||
|
is a tradeoff in potential isses. Also, anyone attempting this strategy really
|
||||||
|
wants to uncover a user, it seems unlikely that this would be performed against
|
||||||
|
every Tor/Kovri/I2P user.
|
||||||
|
|
@ -0,0 +1,65 @@
|
|||||||
|
// 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>
|
||||||
|
|
||||||
|
namespace epee
|
||||||
|
{
|
||||||
|
namespace net_utils
|
||||||
|
{
|
||||||
|
enum class address_type : std::uint8_t
|
||||||
|
{
|
||||||
|
// Do not change values, this will break serialization
|
||||||
|
invalid = 0,
|
||||||
|
ipv4 = 1,
|
||||||
|
ipv6 = 2,
|
||||||
|
i2p = 3,
|
||||||
|
tor = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class zone : std::uint8_t
|
||||||
|
{
|
||||||
|
invalid = 0,
|
||||||
|
public_ = 1, // public is keyword
|
||||||
|
i2p = 2,
|
||||||
|
tor = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
// implementations in src/net_utils_base.cpp
|
||||||
|
|
||||||
|
//! \return String name of zone or "invalid" on error.
|
||||||
|
const char* zone_to_string(zone value) noexcept;
|
||||||
|
|
||||||
|
//! \return `zone` enum of `value` or `zone::invalid` on error.
|
||||||
|
zone zone_from_string(boost::string_ref value) noexcept;
|
||||||
|
} // net_utils
|
||||||
|
} // epee
|
||||||
|
|
@ -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})
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
{};
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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
|
@ -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
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,295 @@
|
|||||||
|
// 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 "net_peerlist.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include <boost/archive/binary_iarchive.hpp>
|
||||||
|
#include <boost/archive/portable_binary_oarchive.hpp>
|
||||||
|
#include <boost/archive/portable_binary_iarchive.hpp>
|
||||||
|
#include <boost/filesystem/operations.hpp>
|
||||||
|
#include <boost/range/join.hpp>
|
||||||
|
#include <boost/serialization/version.hpp>
|
||||||
|
|
||||||
|
#include "net_peerlist_boost_serialization.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nodetool
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr unsigned CURRENT_PEERLIST_STORAGE_ARCHIVE_VER = 6;
|
||||||
|
|
||||||
|
struct by_zone
|
||||||
|
{
|
||||||
|
using zone = epee::net_utils::zone;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool operator()(const T& left, const zone right) const
|
||||||
|
{
|
||||||
|
return left.adr.get_zone() < right;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool operator()(const zone left, const T& right) const
|
||||||
|
{
|
||||||
|
return left < right.adr.get_zone();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename U>
|
||||||
|
bool operator()(const T& left, const U& right) const
|
||||||
|
{
|
||||||
|
return left.adr.get_zone() < right.adr.get_zone();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Elem, typename Archive>
|
||||||
|
std::vector<Elem> load_peers(Archive& a, unsigned ver)
|
||||||
|
{
|
||||||
|
// at v6, we drop existing peerlists, because annoying change
|
||||||
|
if (ver < 6)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
uint64_t size = 0;
|
||||||
|
a & size;
|
||||||
|
|
||||||
|
Elem ple{};
|
||||||
|
|
||||||
|
std::vector<Elem> elems{};
|
||||||
|
elems.reserve(size);
|
||||||
|
while (size--)
|
||||||
|
{
|
||||||
|
a & ple;
|
||||||
|
elems.push_back(std::move(ple));
|
||||||
|
}
|
||||||
|
|
||||||
|
return elems;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Archive, typename Range>
|
||||||
|
void save_peers(Archive& a, const Range& elems)
|
||||||
|
{
|
||||||
|
const uint64_t size = elems.size();
|
||||||
|
a & size;
|
||||||
|
for (const auto& elem : elems)
|
||||||
|
a & elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::vector<T> do_take_zone(std::vector<T>& src, epee::net_utils::zone zone)
|
||||||
|
{
|
||||||
|
const auto start = std::lower_bound(src.begin(), src.end(), zone, by_zone{});
|
||||||
|
const auto end = std::upper_bound(start, src.end(), zone, by_zone{});
|
||||||
|
|
||||||
|
std::vector<T> out{};
|
||||||
|
out.assign(std::make_move_iterator(start), std::make_move_iterator(end));
|
||||||
|
src.erase(start, end);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Container, typename T>
|
||||||
|
void add_peers(Container& dest, std::vector<T>&& src)
|
||||||
|
{
|
||||||
|
dest.insert(std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Container, typename Range>
|
||||||
|
void copy_peers(Container& dest, const Range& src)
|
||||||
|
{
|
||||||
|
std::copy(src.begin(), src.end(), std::back_inserter(dest));
|
||||||
|
}
|
||||||
|
} // anonymous
|
||||||
|
|
||||||
|
struct peerlist_join
|
||||||
|
{
|
||||||
|
const peerlist_types& ours;
|
||||||
|
const peerlist_types& other;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Archive>
|
||||||
|
void serialize(Archive& a, peerlist_types& elem, unsigned ver)
|
||||||
|
{
|
||||||
|
elem.white = load_peers<peerlist_entry>(a, ver);
|
||||||
|
elem.gray = load_peers<peerlist_entry>(a, ver);
|
||||||
|
elem.anchor = load_peers<anchor_peerlist_entry>(a, ver);
|
||||||
|
|
||||||
|
if (ver == 0)
|
||||||
|
{
|
||||||
|
// from v1, we do not store the peer id anymore
|
||||||
|
peerid_type peer_id{};
|
||||||
|
a & peer_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Archive>
|
||||||
|
void serialize(Archive& a, peerlist_join elem, unsigned ver)
|
||||||
|
{
|
||||||
|
save_peers(a, boost::range::join(elem.ours.white, elem.other.white));
|
||||||
|
save_peers(a, boost::range::join(elem.ours.gray, elem.other.gray));
|
||||||
|
save_peers(a, boost::range::join(elem.ours.anchor, elem.other.anchor));
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<peerlist_storage> peerlist_storage::open(std::istream& src, const bool new_format)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
peerlist_storage out{};
|
||||||
|
if (new_format)
|
||||||
|
{
|
||||||
|
boost::archive::portable_binary_iarchive a{src};
|
||||||
|
a >> out.m_types;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::archive::binary_iarchive a{src};
|
||||||
|
a >> out.m_types;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.good())
|
||||||
|
{
|
||||||
|
std::sort(out.m_types.white.begin(), out.m_types.white.end(), by_zone{});
|
||||||
|
std::sort(out.m_types.gray.begin(), out.m_types.gray.end(), by_zone{});
|
||||||
|
std::sort(out.m_types.anchor.begin(), out.m_types.anchor.end(), by_zone{});
|
||||||
|
return {std::move(out)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{}
|
||||||
|
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<peerlist_storage> peerlist_storage::open(const std::string& path)
|
||||||
|
{
|
||||||
|
std::ifstream src_file{};
|
||||||
|
src_file.open( path , std::ios_base::binary | std::ios_base::in);
|
||||||
|
if(src_file.fail())
|
||||||
|
return boost::none;
|
||||||
|
|
||||||
|
boost::optional<peerlist_storage> out = open(src_file, true);
|
||||||
|
if (!out)
|
||||||
|
{
|
||||||
|
// if failed, try reading in unportable mode
|
||||||
|
boost::filesystem::copy_file(path, path + ".unportable", boost::filesystem::copy_option::overwrite_if_exists);
|
||||||
|
src_file.close();
|
||||||
|
src_file.open( path , std::ios_base::binary | std::ios_base::in);
|
||||||
|
if(src_file.fail())
|
||||||
|
return boost::none;
|
||||||
|
|
||||||
|
out = open(src_file, false);
|
||||||
|
if (!out)
|
||||||
|
{
|
||||||
|
// This is different from the `return boost::none` cases above. Those
|
||||||
|
// cases could fail due to bad file permissions, so a shutdown is
|
||||||
|
// likely more appropriate.
|
||||||
|
MWARNING("Failed to load p2p config file, falling back to default config");
|
||||||
|
out.emplace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
peerlist_storage::~peerlist_storage() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool peerlist_storage::store(std::ostream& dest, const peerlist_types& other) const
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::archive::portable_binary_oarchive a{dest};
|
||||||
|
const peerlist_join pj{std::cref(m_types), std::cref(other)};
|
||||||
|
a << pj;
|
||||||
|
return dest.good();
|
||||||
|
}
|
||||||
|
catch (const boost::archive::archive_exception& e)
|
||||||
|
{}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool peerlist_storage::store(const std::string& path, const peerlist_types& other) const
|
||||||
|
{
|
||||||
|
std::ofstream dest_file{};
|
||||||
|
dest_file.open( path , std::ios_base::binary | std::ios_base::out| std::ios::trunc);
|
||||||
|
if(dest_file.fail())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return store(dest_file, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
peerlist_types peerlist_storage::take_zone(epee::net_utils::zone zone)
|
||||||
|
{
|
||||||
|
peerlist_types out{};
|
||||||
|
out.white = do_take_zone(m_types.white, zone);
|
||||||
|
out.gray = do_take_zone(m_types.gray, zone);
|
||||||
|
out.anchor = do_take_zone(m_types.anchor, zone);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool peerlist_manager::init(peerlist_types&& peers, bool allow_local_ip)
|
||||||
|
{
|
||||||
|
CRITICAL_REGION_LOCAL(m_peerlist_lock);
|
||||||
|
|
||||||
|
if (!m_peers_white.empty() || !m_peers_gray.empty() || !m_peers_anchor.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
add_peers(m_peers_white.get<by_addr>(), std::move(peers.white));
|
||||||
|
add_peers(m_peers_gray.get<by_addr>(), std::move(peers.gray));
|
||||||
|
add_peers(m_peers_anchor.get<by_addr>(), std::move(peers.anchor));
|
||||||
|
m_allow_local_ip = allow_local_ip;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void peerlist_manager::get_peerlist(std::vector<peerlist_entry>& pl_gray, std::vector<peerlist_entry>& pl_white)
|
||||||
|
{
|
||||||
|
CRITICAL_REGION_LOCAL(m_peerlist_lock);
|
||||||
|
copy_peers(pl_gray, m_peers_gray.get<by_addr>());
|
||||||
|
copy_peers(pl_white, m_peers_white.get<by_addr>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void peerlist_manager::get_peerlist(peerlist_types& peers)
|
||||||
|
{
|
||||||
|
CRITICAL_REGION_LOCAL(m_peerlist_lock);
|
||||||
|
peers.white.reserve(peers.white.size() + m_peers_white.size());
|
||||||
|
peers.gray.reserve(peers.gray.size() + m_peers_gray.size());
|
||||||
|
peers.anchor.reserve(peers.anchor.size() + m_peers_anchor.size());
|
||||||
|
|
||||||
|
copy_peers(peers.white, m_peers_white.get<by_addr>());
|
||||||
|
copy_peers(peers.gray, m_peers_gray.get<by_addr>());
|
||||||
|
copy_peers(peers.anchor, m_peers_anchor.get<by_addr>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_CLASS_VERSION(nodetool::peerlist_types, nodetool::CURRENT_PEERLIST_STORAGE_ARCHIVE_VER);
|
||||||
|
BOOST_CLASS_VERSION(nodetool::peerlist_join, nodetool::CURRENT_PEERLIST_STORAGE_ARCHIVE_VER);
|
||||||
|
|
@ -0,0 +1,745 @@
|
|||||||
|
// 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 <atomic>
|
||||||
|
#include <boost/archive/portable_binary_oarchive.hpp>
|
||||||
|
#include <boost/archive/portable_binary_iarchive.hpp>
|
||||||
|
#include <boost/asio/buffer.hpp>
|
||||||
|
#include <boost/asio/io_service.hpp>
|
||||||
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
#include <boost/asio/read.hpp>
|
||||||
|
#include <boost/asio/write.hpp>
|
||||||
|
#include <boost/endian/conversion.hpp>
|
||||||
|
#include <boost/system/error_code.hpp>
|
||||||
|
#include <boost/thread/thread.hpp>
|
||||||
|
#include <cstring>
|
||||||
|
#include <functional>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "net/error.h"
|
||||||
|
#include "net/net_utils_base.h"
|
||||||
|
#include "net/socks.h"
|
||||||
|
#include "net/parse.h"
|
||||||
|
#include "net/tor_address.h"
|
||||||
|
#include "p2p/net_peerlist_boost_serialization.h"
|
||||||
|
#include "serialization/keyvalue_serialization.h"
|
||||||
|
#include "storages/portable_storage.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
static constexpr const char v2_onion[] =
|
||||||
|
"xmrto2bturnore26.onion";
|
||||||
|
static constexpr const char v3_onion[] =
|
||||||
|
"vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, constants)
|
||||||
|
{
|
||||||
|
static_assert(!net::tor_address::is_local(), "bad is_local() response");
|
||||||
|
static_assert(!net::tor_address::is_loopback(), "bad is_loopback() response");
|
||||||
|
static_assert(net::tor_address::get_type_id() == epee::net_utils::address_type::tor, "bad get_type_id() response");
|
||||||
|
|
||||||
|
EXPECT_FALSE(net::tor_address::is_local());
|
||||||
|
EXPECT_FALSE(net::tor_address::is_loopback());
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::tor, net::tor_address::get_type_id());
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::tor, net::tor_address::get_type_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, invalid)
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(net::tor_address::make("").has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(":").has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(".onion").has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(".onion:").has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(v2_onion + 1).has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(v3_onion + 1).has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(boost::string_ref{v2_onion, sizeof(v2_onion) - 2}).has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(boost::string_ref{v3_onion, sizeof(v3_onion) - 2}).has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(std::string{v2_onion} + ":-").has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(std::string{v2_onion} + ":900a").has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":65536").has_error());
|
||||||
|
EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":-1").has_error());
|
||||||
|
|
||||||
|
std::string onion{v3_onion};
|
||||||
|
onion.at(10) = 1;
|
||||||
|
EXPECT_TRUE(net::tor_address::make(onion).has_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, unblockable_types)
|
||||||
|
{
|
||||||
|
net::tor_address tor{};
|
||||||
|
|
||||||
|
ASSERT_NE(nullptr, tor.host_str());
|
||||||
|
EXPECT_STREQ("<unknown tor host>", tor.host_str());
|
||||||
|
EXPECT_STREQ("<unknown tor host>", tor.str().c_str());
|
||||||
|
EXPECT_EQ(0u, tor.port());
|
||||||
|
EXPECT_TRUE(tor.is_unknown());
|
||||||
|
EXPECT_FALSE(tor.is_local());
|
||||||
|
EXPECT_FALSE(tor.is_loopback());
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::tor, tor.get_type_id());
|
||||||
|
EXPECT_EQ(epee::net_utils::zone::tor, tor.get_zone());
|
||||||
|
|
||||||
|
tor = net::tor_address::unknown();
|
||||||
|
ASSERT_NE(nullptr, tor.host_str());
|
||||||
|
EXPECT_STREQ("<unknown tor host>", tor.host_str());
|
||||||
|
EXPECT_STREQ("<unknown tor host>", tor.str().c_str());
|
||||||
|
EXPECT_EQ(0u, tor.port());
|
||||||
|
EXPECT_TRUE(tor.is_unknown());
|
||||||
|
EXPECT_FALSE(tor.is_local());
|
||||||
|
EXPECT_FALSE(tor.is_loopback());
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::tor, tor.get_type_id());
|
||||||
|
EXPECT_EQ(epee::net_utils::zone::tor, tor.get_zone());
|
||||||
|
|
||||||
|
EXPECT_EQ(net::tor_address{}, net::tor_address::unknown());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, valid)
|
||||||
|
{
|
||||||
|
const auto address1 = net::tor_address::make(v3_onion);
|
||||||
|
|
||||||
|
ASSERT_TRUE(address1.has_value());
|
||||||
|
EXPECT_EQ(0u, address1->port());
|
||||||
|
EXPECT_STREQ(v3_onion, address1->host_str());
|
||||||
|
EXPECT_STREQ(v3_onion, address1->str().c_str());
|
||||||
|
EXPECT_TRUE(address1->is_blockable());
|
||||||
|
|
||||||
|
net::tor_address address2{*address1};
|
||||||
|
|
||||||
|
EXPECT_EQ(0u, address2.port());
|
||||||
|
EXPECT_STREQ(v3_onion, address2.host_str());
|
||||||
|
EXPECT_STREQ(v3_onion, address2.str().c_str());
|
||||||
|
EXPECT_TRUE(address2.is_blockable());
|
||||||
|
EXPECT_TRUE(address2.equal(*address1));
|
||||||
|
EXPECT_TRUE(address1->equal(address2));
|
||||||
|
EXPECT_TRUE(address2 == *address1);
|
||||||
|
EXPECT_TRUE(*address1 == address2);
|
||||||
|
EXPECT_FALSE(address2 != *address1);
|
||||||
|
EXPECT_FALSE(*address1 != address2);
|
||||||
|
EXPECT_TRUE(address2.is_same_host(*address1));
|
||||||
|
EXPECT_TRUE(address1->is_same_host(address2));
|
||||||
|
EXPECT_FALSE(address2.less(*address1));
|
||||||
|
EXPECT_FALSE(address1->less(address2));
|
||||||
|
|
||||||
|
address2 = MONERO_UNWRAP(net::tor_address::make(std::string{v2_onion} + ":6545"));
|
||||||
|
|
||||||
|
EXPECT_EQ(6545, address2.port());
|
||||||
|
EXPECT_STREQ(v2_onion, address2.host_str());
|
||||||
|
EXPECT_EQ(std::string{v2_onion} + ":6545", address2.str().c_str());
|
||||||
|
EXPECT_TRUE(address2.is_blockable());
|
||||||
|
EXPECT_FALSE(address2.equal(*address1));
|
||||||
|
EXPECT_FALSE(address1->equal(address2));
|
||||||
|
EXPECT_FALSE(address2 == *address1);
|
||||||
|
EXPECT_FALSE(*address1 == address2);
|
||||||
|
EXPECT_TRUE(address2 != *address1);
|
||||||
|
EXPECT_TRUE(*address1 != address2);
|
||||||
|
EXPECT_FALSE(address2.is_same_host(*address1));
|
||||||
|
EXPECT_FALSE(address1->is_same_host(address2));
|
||||||
|
EXPECT_FALSE(address2.less(*address1));
|
||||||
|
EXPECT_TRUE(address1->less(address2));
|
||||||
|
|
||||||
|
address2 = MONERO_UNWRAP(net::tor_address::make(std::string{v3_onion} + ":", 65535));
|
||||||
|
|
||||||
|
EXPECT_EQ(65535, address2.port());
|
||||||
|
EXPECT_STREQ(v3_onion, address2.host_str());
|
||||||
|
EXPECT_EQ(std::string{v3_onion} + ":65535", address2.str().c_str());
|
||||||
|
EXPECT_TRUE(address2.is_blockable());
|
||||||
|
EXPECT_FALSE(address2.equal(*address1));
|
||||||
|
EXPECT_FALSE(address1->equal(address2));
|
||||||
|
EXPECT_FALSE(address2 == *address1);
|
||||||
|
EXPECT_FALSE(*address1 == address2);
|
||||||
|
EXPECT_TRUE(address2 != *address1);
|
||||||
|
EXPECT_TRUE(*address1 != address2);
|
||||||
|
EXPECT_TRUE(address2.is_same_host(*address1));
|
||||||
|
EXPECT_TRUE(address1->is_same_host(address2));
|
||||||
|
EXPECT_FALSE(address2.less(*address1));
|
||||||
|
EXPECT_TRUE(address1->less(address2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, generic_network_address)
|
||||||
|
{
|
||||||
|
const epee::net_utils::network_address tor1{MONERO_UNWRAP(net::tor_address::make(v3_onion, 8080))};
|
||||||
|
const epee::net_utils::network_address tor2{MONERO_UNWRAP(net::tor_address::make(v3_onion, 8080))};
|
||||||
|
const epee::net_utils::network_address ip{epee::net_utils::ipv4_network_address{100, 200}};
|
||||||
|
|
||||||
|
EXPECT_EQ(tor1, tor2);
|
||||||
|
EXPECT_NE(ip, tor1);
|
||||||
|
EXPECT_LT(ip, tor1);
|
||||||
|
|
||||||
|
EXPECT_STREQ(v3_onion, tor1.host_str().c_str());
|
||||||
|
EXPECT_EQ(std::string{v3_onion} + ":8080", tor1.str());
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::tor, tor1.get_type_id());
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::tor, tor2.get_type_id());
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::ipv4, ip.get_type_id());
|
||||||
|
EXPECT_EQ(epee::net_utils::zone::tor, tor1.get_zone());
|
||||||
|
EXPECT_EQ(epee::net_utils::zone::tor, tor2.get_zone());
|
||||||
|
EXPECT_EQ(epee::net_utils::zone::public_, ip.get_zone());
|
||||||
|
EXPECT_TRUE(tor1.is_blockable());
|
||||||
|
EXPECT_TRUE(tor2.is_blockable());
|
||||||
|
EXPECT_TRUE(ip.is_blockable());
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct test_command
|
||||||
|
{
|
||||||
|
net::tor_address tor;
|
||||||
|
|
||||||
|
BEGIN_KV_SERIALIZE_MAP()
|
||||||
|
KV_SERIALIZE(tor);
|
||||||
|
END_KV_SERIALIZE_MAP()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, epee_serializev_v2)
|
||||||
|
{
|
||||||
|
std::string buffer{};
|
||||||
|
{
|
||||||
|
test_command command{MONERO_UNWRAP(net::tor_address::make(v2_onion, 10))};
|
||||||
|
EXPECT_FALSE(command.tor.is_unknown());
|
||||||
|
EXPECT_NE(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(v2_onion, command.tor.host_str());
|
||||||
|
EXPECT_EQ(10u, command.tor.port());
|
||||||
|
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
EXPECT_TRUE(command.store(stg));
|
||||||
|
EXPECT_TRUE(stg.store_to_binary(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
test_command command{};
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(command.tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str());
|
||||||
|
EXPECT_EQ(0u, command.tor.port());
|
||||||
|
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
EXPECT_TRUE(stg.load_from_binary(buffer));
|
||||||
|
EXPECT_TRUE(command.load(stg));
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(command.tor.is_unknown());
|
||||||
|
EXPECT_NE(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(v2_onion, command.tor.host_str());
|
||||||
|
EXPECT_EQ(10u, command.tor.port());
|
||||||
|
|
||||||
|
// make sure that exceeding max buffer doesn't destroy tor_address::_load
|
||||||
|
{
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
stg.load_from_binary(buffer);
|
||||||
|
|
||||||
|
std::string host{};
|
||||||
|
ASSERT_TRUE(stg.get_value("host", host, stg.open_section("tor", nullptr, false)));
|
||||||
|
EXPECT_EQ(std::strlen(v2_onion), host.size());
|
||||||
|
|
||||||
|
host.push_back('k');
|
||||||
|
EXPECT_TRUE(stg.set_value("host", host, stg.open_section("tor", nullptr, false)));
|
||||||
|
EXPECT_TRUE(command.load(stg)); // poor error reporting from `KV_SERIALIZE`
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(command.tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str());
|
||||||
|
EXPECT_EQ(0u, command.tor.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, epee_serializev_v3)
|
||||||
|
{
|
||||||
|
std::string buffer{};
|
||||||
|
{
|
||||||
|
test_command command{MONERO_UNWRAP(net::tor_address::make(v3_onion, 10))};
|
||||||
|
EXPECT_FALSE(command.tor.is_unknown());
|
||||||
|
EXPECT_NE(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(v3_onion, command.tor.host_str());
|
||||||
|
EXPECT_EQ(10u, command.tor.port());
|
||||||
|
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
EXPECT_TRUE(command.store(stg));
|
||||||
|
EXPECT_TRUE(stg.store_to_binary(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
test_command command{};
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(command.tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str());
|
||||||
|
EXPECT_EQ(0u, command.tor.port());
|
||||||
|
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
EXPECT_TRUE(stg.load_from_binary(buffer));
|
||||||
|
EXPECT_TRUE(command.load(stg));
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(command.tor.is_unknown());
|
||||||
|
EXPECT_NE(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(v3_onion, command.tor.host_str());
|
||||||
|
EXPECT_EQ(10u, command.tor.port());
|
||||||
|
|
||||||
|
// make sure that exceeding max buffer doesn't destroy tor_address::_load
|
||||||
|
{
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
stg.load_from_binary(buffer);
|
||||||
|
|
||||||
|
std::string host{};
|
||||||
|
ASSERT_TRUE(stg.get_value("host", host, stg.open_section("tor", nullptr, false)));
|
||||||
|
EXPECT_EQ(std::strlen(v3_onion), host.size());
|
||||||
|
|
||||||
|
host.push_back('k');
|
||||||
|
EXPECT_TRUE(stg.set_value("host", host, stg.open_section("tor", nullptr, false)));
|
||||||
|
EXPECT_TRUE(command.load(stg)); // poor error reporting from `KV_SERIALIZE`
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(command.tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STRNE(v3_onion, command.tor.host_str());
|
||||||
|
EXPECT_EQ(0u, command.tor.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, epee_serialize_unknown)
|
||||||
|
{
|
||||||
|
std::string buffer{};
|
||||||
|
{
|
||||||
|
test_command command{net::tor_address::unknown()};
|
||||||
|
EXPECT_TRUE(command.tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str());
|
||||||
|
EXPECT_EQ(0u, command.tor.port());
|
||||||
|
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
EXPECT_TRUE(command.store(stg));
|
||||||
|
EXPECT_TRUE(stg.store_to_binary(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
test_command command{};
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(command.tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STRNE(v3_onion, command.tor.host_str());
|
||||||
|
EXPECT_EQ(0u, command.tor.port());
|
||||||
|
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
EXPECT_TRUE(stg.load_from_binary(buffer));
|
||||||
|
EXPECT_TRUE(command.load(stg));
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(command.tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str());
|
||||||
|
EXPECT_EQ(0u, command.tor.port());
|
||||||
|
|
||||||
|
// make sure that exceeding max buffer doesn't destroy tor_address::_load
|
||||||
|
{
|
||||||
|
epee::serialization::portable_storage stg{};
|
||||||
|
stg.load_from_binary(buffer);
|
||||||
|
|
||||||
|
std::string host{};
|
||||||
|
ASSERT_TRUE(stg.get_value("host", host, stg.open_section("tor", nullptr, false)));
|
||||||
|
EXPECT_EQ(std::strlen(net::tor_address::unknown_str()), host.size());
|
||||||
|
|
||||||
|
host.push_back('k');
|
||||||
|
EXPECT_TRUE(stg.set_value("host", host, stg.open_section("tor", nullptr, false)));
|
||||||
|
EXPECT_TRUE(command.load(stg)); // poor error reporting from `KV_SERIALIZE`
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(command.tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, command.tor);
|
||||||
|
EXPECT_STRNE(v3_onion, command.tor.host_str());
|
||||||
|
EXPECT_EQ(0u, command.tor.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, boost_serialize_v2)
|
||||||
|
{
|
||||||
|
std::string buffer{};
|
||||||
|
{
|
||||||
|
const net::tor_address tor = MONERO_UNWRAP(net::tor_address::make(v2_onion, 10));
|
||||||
|
EXPECT_FALSE(tor.is_unknown());
|
||||||
|
EXPECT_NE(net::tor_address{}, tor);
|
||||||
|
EXPECT_STREQ(v2_onion, tor.host_str());
|
||||||
|
EXPECT_EQ(10u, tor.port());
|
||||||
|
|
||||||
|
std::ostringstream stream{};
|
||||||
|
{
|
||||||
|
boost::archive::portable_binary_oarchive archive{stream};
|
||||||
|
archive << tor;
|
||||||
|
}
|
||||||
|
buffer = stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
net::tor_address tor{};
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), tor.host_str());
|
||||||
|
EXPECT_EQ(0u, tor.port());
|
||||||
|
|
||||||
|
std::istringstream stream{buffer};
|
||||||
|
boost::archive::portable_binary_iarchive archive{stream};
|
||||||
|
archive >> tor;
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(tor.is_unknown());
|
||||||
|
EXPECT_NE(net::tor_address{}, tor);
|
||||||
|
EXPECT_STREQ(v2_onion, tor.host_str());
|
||||||
|
EXPECT_EQ(10u, tor.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, boost_serialize_v3)
|
||||||
|
{
|
||||||
|
std::string buffer{};
|
||||||
|
{
|
||||||
|
const net::tor_address tor = MONERO_UNWRAP(net::tor_address::make(v3_onion, 10));
|
||||||
|
EXPECT_FALSE(tor.is_unknown());
|
||||||
|
EXPECT_NE(net::tor_address{}, tor);
|
||||||
|
EXPECT_STREQ(v3_onion, tor.host_str());
|
||||||
|
EXPECT_EQ(10u, tor.port());
|
||||||
|
|
||||||
|
std::ostringstream stream{};
|
||||||
|
{
|
||||||
|
boost::archive::portable_binary_oarchive archive{stream};
|
||||||
|
archive << tor;
|
||||||
|
}
|
||||||
|
buffer = stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
net::tor_address tor{};
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), tor.host_str());
|
||||||
|
EXPECT_EQ(0u, tor.port());
|
||||||
|
|
||||||
|
std::istringstream stream{buffer};
|
||||||
|
boost::archive::portable_binary_iarchive archive{stream};
|
||||||
|
archive >> tor;
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(tor.is_unknown());
|
||||||
|
EXPECT_NE(net::tor_address{}, tor);
|
||||||
|
EXPECT_STREQ(v3_onion, tor.host_str());
|
||||||
|
EXPECT_EQ(10u, tor.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(tor_address, boost_serialize_unknown)
|
||||||
|
{
|
||||||
|
std::string buffer{};
|
||||||
|
{
|
||||||
|
const net::tor_address tor{};
|
||||||
|
EXPECT_TRUE(tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address::unknown(), tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), tor.host_str());
|
||||||
|
EXPECT_EQ(0u, tor.port());
|
||||||
|
|
||||||
|
std::ostringstream stream{};
|
||||||
|
{
|
||||||
|
boost::archive::portable_binary_oarchive archive{stream};
|
||||||
|
archive << tor;
|
||||||
|
}
|
||||||
|
buffer = stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
net::tor_address tor{};
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address{}, tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), tor.host_str());
|
||||||
|
EXPECT_EQ(0u, tor.port());
|
||||||
|
|
||||||
|
std::istringstream stream{buffer};
|
||||||
|
boost::archive::portable_binary_iarchive archive{stream};
|
||||||
|
archive >> tor;
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(tor.is_unknown());
|
||||||
|
EXPECT_EQ(net::tor_address::unknown(), tor);
|
||||||
|
EXPECT_STREQ(net::tor_address::unknown_str(), tor.host_str());
|
||||||
|
EXPECT_EQ(0u, tor.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(get_network_address, onion)
|
||||||
|
{
|
||||||
|
expect<epee::net_utils::network_address> address =
|
||||||
|
net::get_network_address("onion", 0);
|
||||||
|
EXPECT_EQ(net::error::unsupported_address, address);
|
||||||
|
|
||||||
|
address = net::get_network_address(".onion", 0);
|
||||||
|
EXPECT_EQ(net::error::invalid_tor_address, address);
|
||||||
|
|
||||||
|
address = net::get_network_address(v3_onion, 1000);
|
||||||
|
ASSERT_TRUE(bool(address));
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::tor, address->get_type_id());
|
||||||
|
EXPECT_STREQ(v3_onion, address->host_str().c_str());
|
||||||
|
EXPECT_EQ(std::string{v3_onion} + ":1000", address->str());
|
||||||
|
|
||||||
|
address = net::get_network_address(std::string{v3_onion} + ":2000", 1000);
|
||||||
|
ASSERT_TRUE(bool(address));
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::tor, address->get_type_id());
|
||||||
|
EXPECT_STREQ(v3_onion, address->host_str().c_str());
|
||||||
|
EXPECT_EQ(std::string{v3_onion} + ":2000", address->str());
|
||||||
|
|
||||||
|
address = net::get_network_address(std::string{v3_onion} + ":65536", 1000);
|
||||||
|
EXPECT_EQ(net::error::invalid_port, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(get_network_address, ipv4)
|
||||||
|
{
|
||||||
|
expect<epee::net_utils::network_address> address =
|
||||||
|
net::get_network_address("0.0.0.", 0);
|
||||||
|
EXPECT_EQ(net::error::unsupported_address, address);
|
||||||
|
|
||||||
|
address = net::get_network_address("0.0.0.257", 0);
|
||||||
|
EXPECT_EQ(net::error::unsupported_address, address);
|
||||||
|
|
||||||
|
address = net::get_network_address("0.0.0.254", 1000);
|
||||||
|
ASSERT_TRUE(bool(address));
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::ipv4, address->get_type_id());
|
||||||
|
EXPECT_STREQ("0.0.0.254", address->host_str().c_str());
|
||||||
|
EXPECT_STREQ("0.0.0.254:1000", address->str().c_str());
|
||||||
|
|
||||||
|
address = net::get_network_address("23.0.0.254:2000", 1000);
|
||||||
|
ASSERT_TRUE(bool(address));
|
||||||
|
EXPECT_EQ(epee::net_utils::address_type::ipv4, address->get_type_id());
|
||||||
|
EXPECT_STREQ("23.0.0.254", address->host_str().c_str());
|
||||||
|
EXPECT_STREQ("23.0.0.254:2000", address->str().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
using stream_type = boost::asio::ip::tcp;
|
||||||
|
|
||||||
|
struct io_thread
|
||||||
|
{
|
||||||
|
boost::asio::io_service io_service;
|
||||||
|
boost::asio::io_service::work work;
|
||||||
|
stream_type::socket server;
|
||||||
|
stream_type::acceptor acceptor;
|
||||||
|
boost::thread io;
|
||||||
|
std::atomic<bool> connected;
|
||||||
|
|
||||||
|
io_thread()
|
||||||
|
: io_service(),
|
||||||
|
work(io_service),
|
||||||
|
server(io_service),
|
||||||
|
acceptor(io_service),
|
||||||
|
io([this] () { try { this->io_service.run(); } catch (const std::exception& e) { MERROR(e.what()); }}),
|
||||||
|
connected(false)
|
||||||
|
{
|
||||||
|
acceptor.open(boost::asio::ip::tcp::v4());
|
||||||
|
acceptor.bind(stream_type::endpoint{boost::asio::ip::tcp::v4(), 0});
|
||||||
|
acceptor.listen();
|
||||||
|
acceptor.async_accept(server, [this] (boost::system::error_code error) {
|
||||||
|
this->connected = true;
|
||||||
|
if (error)
|
||||||
|
throw boost::system::system_error{error};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
~io_thread() noexcept
|
||||||
|
{
|
||||||
|
io_service.stop();
|
||||||
|
if (io.joinable())
|
||||||
|
io.join();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct checked_client
|
||||||
|
{
|
||||||
|
std::atomic<bool>* called_;
|
||||||
|
bool expected_;
|
||||||
|
|
||||||
|
void operator()(boost::system::error_code error, net::socks::client::stream_type::socket&&) const
|
||||||
|
{
|
||||||
|
EXPECT_EQ(expected_, bool(error)) << "Socks server: " << error.message();
|
||||||
|
ASSERT_TRUE(called_ != nullptr);
|
||||||
|
(*called_) = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(socks_client, unsupported_command)
|
||||||
|
{
|
||||||
|
boost::asio::io_service io_service{};
|
||||||
|
stream_type::socket client{io_service};
|
||||||
|
|
||||||
|
auto test_client = net::socks::make_connect_client(
|
||||||
|
std::move(client), net::socks::version::v4, std::bind( [] {} )
|
||||||
|
);
|
||||||
|
ASSERT_TRUE(bool(test_client));
|
||||||
|
EXPECT_TRUE(test_client->buffer().empty());
|
||||||
|
|
||||||
|
EXPECT_FALSE(test_client->set_connect_command("example.com", 8080));
|
||||||
|
EXPECT_TRUE(test_client->buffer().empty());
|
||||||
|
|
||||||
|
EXPECT_FALSE(test_client->set_resolve_command("example.com"));
|
||||||
|
EXPECT_TRUE(test_client->buffer().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(socks_client, no_command)
|
||||||
|
{
|
||||||
|
boost::asio::io_service io_service{};
|
||||||
|
stream_type::socket client{io_service};
|
||||||
|
|
||||||
|
auto test_client = net::socks::make_connect_client(
|
||||||
|
std::move(client), net::socks::version::v4a, std::bind( [] {} )
|
||||||
|
);
|
||||||
|
ASSERT_TRUE(bool(test_client));
|
||||||
|
EXPECT_FALSE(net::socks::client::send(std::move(test_client)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(socks_client, connect_command)
|
||||||
|
{
|
||||||
|
io_thread io{};
|
||||||
|
stream_type::socket client{io.io_service};
|
||||||
|
|
||||||
|
std::atomic<bool> called{false};
|
||||||
|
auto test_client = net::socks::make_connect_client(
|
||||||
|
std::move(client), net::socks::version::v4a, checked_client{std::addressof(called), false}
|
||||||
|
);
|
||||||
|
ASSERT_TRUE(bool(test_client));
|
||||||
|
|
||||||
|
ASSERT_TRUE(test_client->set_connect_command("example.com", 8080));
|
||||||
|
EXPECT_FALSE(test_client->buffer().empty());
|
||||||
|
ASSERT_TRUE(net::socks::client::connect_and_send(std::move(test_client), io.acceptor.local_endpoint()));
|
||||||
|
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));
|
||||||
|
|
||||||
|
// yikes!
|
||||||
|
while (!called);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(socks_client, connect_command_failed)
|
||||||
|
{
|
||||||
|
io_thread io{};
|
||||||
|
stream_type::socket client{io.io_service};
|
||||||
|
|
||||||
|
std::atomic<bool> called{false};
|
||||||
|
auto test_client = net::socks::make_connect_client(
|
||||||
|
std::move(client), net::socks::version::v4, checked_client{std::addressof(called), true}
|
||||||
|
);
|
||||||
|
ASSERT_TRUE(bool(test_client));
|
||||||
|
|
||||||
|
ASSERT_TRUE(
|
||||||
|
test_client->set_connect_command(
|
||||||
|
epee::net_utils::ipv4_network_address{boost::endian::native_to_big(std::uint32_t(5000)), 3000}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
EXPECT_FALSE(test_client->buffer().empty());
|
||||||
|
ASSERT_TRUE(net::socks::client::connect_and_send(std::move(test_client), io.acceptor.local_endpoint()));
|
||||||
|
while (!io.connected);
|
||||||
|
|
||||||
|
const std::uint8_t expected_bytes[] = {
|
||||||
|
4, 1, 0x0b, 0xb8, 0x00, 0x00, 0x13, 0x88, 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));
|
||||||
|
|
||||||
|
// yikes!
|
||||||
|
while (!called);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(socks_client, resolve_command)
|
||||||
|
{
|
||||||
|
static std::uint8_t reply_bytes[] = {0, 90, 0, 0, 0xff, 0, 0xad, 0};
|
||||||
|
|
||||||
|
struct resolve_client : net::socks::client
|
||||||
|
{
|
||||||
|
std::atomic<unsigned> called_;
|
||||||
|
bool expected_;
|
||||||
|
|
||||||
|
resolve_client(stream_type::socket&& proxy)
|
||||||
|
: net::socks::client(std::move(proxy), net::socks::version::v4a_tor)
|
||||||
|
, called_(0)
|
||||||
|
, expected_(false)
|
||||||
|
{};
|
||||||
|
|
||||||
|
virtual void done(boost::system::error_code error, std::shared_ptr<client> self) override
|
||||||
|
{
|
||||||
|
EXPECT_EQ(this, self.get());
|
||||||
|
EXPECT_EQ(expected_, bool(error)) << "Resolve failure: " << error.message();
|
||||||
|
|
||||||
|
if (!error)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(sizeof(reply_bytes), buffer().size());
|
||||||
|
EXPECT_EQ(0u, std::memcmp(buffer().data(), reply_bytes, sizeof(reply_bytes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
++called_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
io_thread io{};
|
||||||
|
stream_type::socket client{io.io_service};
|
||||||
|
|
||||||
|
auto test_client = std::make_shared<resolve_client>(std::move(client));
|
||||||
|
ASSERT_TRUE(bool(test_client));
|
||||||
|
|
||||||
|
ASSERT_TRUE(test_client->set_resolve_command("example.com"));
|
||||||
|
EXPECT_FALSE(test_client->buffer().empty());
|
||||||
|
ASSERT_TRUE(net::socks::client::connect_and_send(test_client, io.acceptor.local_endpoint()));
|
||||||
|
while (!io.connected);
|
||||||
|
|
||||||
|
const std::uint8_t expected_bytes[] = {
|
||||||
|
4, 0xf0, 0x00, 0x00, 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);
|
||||||
|
|
||||||
|
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||||
|
|
||||||
|
// yikes!
|
||||||
|
while (test_client->called_ == 0);
|
||||||
|
|
||||||
|
test_client->expected_ = true;
|
||||||
|
ASSERT_TRUE(test_client->set_resolve_command("example.com"));
|
||||||
|
EXPECT_FALSE(test_client->buffer().empty());
|
||||||
|
ASSERT_TRUE(net::socks::client::send(test_client));
|
||||||
|
|
||||||
|
boost::asio::read(io.server, boost::asio::buffer(actual_bytes));
|
||||||
|
EXPECT_TRUE(std::memcmp(expected_bytes, actual_bytes, sizeof(actual_bytes)) == 0);
|
||||||
|
|
||||||
|
reply_bytes[1] = 91;
|
||||||
|
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||||
|
|
||||||
|
// yikes!
|
||||||
|
while (test_client->called_ == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue