mirror of
https://github.com/monero-project/monero.git
synced 2024-12-24 22:49:25 -05:00
Add Socks v5 support to daemon and wallet
This commit is contained in:
parent
caa62bc9ea
commit
64a613e838
204
docs/proxies.md
Normal file
204
docs/proxies.md
Normal file
@ -0,0 +1,204 @@
|
||||
# Proxy usage in the Monero ecosystem
|
||||
The CLI/RPC wallets and daemon both support proxies and use the same parameters
|
||||
to configure them. Currently socks 4, 4a, and 5 are supported and can be
|
||||
selected with command-line options.
|
||||
|
||||
## Wallet
|
||||
The CLI and RPC wallets support proxies via the `--proxy` option. The format
|
||||
for usage is `[socks5://[user:pass]]host:port`. The square brackets indicate
|
||||
an optional portion. This option can only be specified once. Examples:
|
||||
|
||||
```
|
||||
--proxy 192.168.0.10:1050
|
||||
--proxy socks5://192.168.0.10:1050
|
||||
--proxy socks5://username:password@192.168.0.10:1050
|
||||
--proxy [::1]:1050
|
||||
--proxy socks5://[::1]:1050
|
||||
--proxy socks5://username:password@[::1]:1050
|
||||
```
|
||||
|
||||
The first connects to `192.168.0.10` on port `1050` using socks 4a. The second
|
||||
connects to the same location using socks 5. The third uses socks 5 at the same
|
||||
location and sends user authentication if prompted by the proxy server. The
|
||||
last three are identical to the first 3, except an IPv6 address is used
|
||||
instead. While IPv6 connections are invalid for Socks 4 and 4a, the proxy
|
||||
server itself can be connected using IPv6.
|
||||
|
||||
The username and password fields both support "percent-encoding" for special
|
||||
character support. As an example, `%40` gets converted to `@`, such that
|
||||
`username:p%40ssword` gets converted to `username:p@ssword`. This allows that
|
||||
specific character to be used; specifying the character directly will
|
||||
incorrectly change the specification of the hostname.
|
||||
|
||||
> NOTE: The username+password will show up in the process list and can be read
|
||||
> by other programs. It is recommended that `--config-file` be used to store
|
||||
> username+password options. The format for a config file is `option=value`,
|
||||
> so in this example the file would contain:
|
||||
> `proxy=socks5://username:password@192.168.0.10:1080`.
|
||||
|
||||
The CLI and RPC wallets currently reject hosts that do **NOT** end in`.onion`
|
||||
or `.i2p` **unless** `--daemon-ssl-ca-certificates` or
|
||||
`--daemon-ssl-allowed-fingerprints` is used. If an onion or i2p address is used,
|
||||
the hostname contains the certificate verification, providing decent security
|
||||
against man-in-the-middle (MitM) attacks. The two `--daemon-ssl-*` options
|
||||
support specifying exact certificates, also preventing MitM attacks.
|
||||
|
||||
> Perhaps the wallets should be relaxed to allow system-CA checks, but for now
|
||||
> certificates must be strictly provided.
|
||||
|
||||
## Daemon
|
||||
The daemon has two options for proxies `--proxy` and `--tx-proxy` which can be
|
||||
used in isolation or together. The `--proxy` option controls how
|
||||
IPv4/IPv6/hostname connections are performed, whereas `--tx-proxy` controls
|
||||
how local transactions are relayed. Both options support Socks 4, 4a, and 5.
|
||||
|
||||
### `--proxy`
|
||||
This option should be used when outbound connections to IPv4/IPv6 addresses and
|
||||
hostnames (other than `.onion` `.i2p`) need to be proxied. Common examples
|
||||
include using Tor exit nodes or a VPN to conceal your local IP. This option
|
||||
will **not** use Tor or I2P hidden services for P2P connections; this is
|
||||
primarily used for proxying standard IPv4 or IPv6 connections to some remote
|
||||
host. Hidden services are not used because this is designed to be more general
|
||||
purpose (i.e. a standard socks VPN can be used).
|
||||
|
||||
> An additional option for hidden services (separate from `--tx-proxy`) could
|
||||
> arguably be added, which could optionally turn off IPv4/IPv6 connections for
|
||||
> P2P.
|
||||
|
||||
The format for `--proxy` usage: `[socks5://[user:pass]@127.0.0.1`. The square
|
||||
bracket indicate optional portion. See [wallet](#wallet) section above for
|
||||
examples and other information on the format. The option can only be specified
|
||||
once. The restrictions for MitM attacks apply only to the wallet usage, and not
|
||||
to the daemon.
|
||||
|
||||
> When using `--proxy`, inbound connections will be impossible unless the
|
||||
> proxy server is somehow setup to forward connections. This setup is a
|
||||
> difficult because each outgoing socks connections can have a unique binding
|
||||
> port. Such a setup is currently out-of-scope for this document.
|
||||
|
||||
### `--tx-proxy`
|
||||
This option should be used to specify a proxy that can resolve hidden service
|
||||
hostnames, so that local transactions can be forwarded over a privacy
|
||||
preserving network. Currently only Tor or I2P hidden services are supported.
|
||||
This option be specified multiple times, but only once per network (see below).
|
||||
|
||||
The format for `--tx-proxy` is
|
||||
`network,[socks5://[user:pass@]]ip:port[,max_connections][,disable_noise]`.
|
||||
Examples:
|
||||
|
||||
```
|
||||
--tx-proxy tor,127.0.0.1:1050
|
||||
--tx-proxy tor,127.0.0.1:1050,100
|
||||
--tx-proxy tor,127.0.0.1:1050,disable_noise
|
||||
--tx-proxy tor,127.0.0.1:1050,100,disable_noise
|
||||
--tx-proxy tor,socks5://127.0.0.1:1050
|
||||
--tx-proxy tor,socks5://127.0.0.1:1050,100
|
||||
--tx-proxy tor,socks5://127.0.0.1:1050,disable_noise
|
||||
--tx-proxy tor,socks5://127.0.0.1:1050,100,disable_noise
|
||||
--tx-proxy tor,socks5://username:password@127.0.0.1:1050
|
||||
--tx-proxy tor,socks5://username:password@127.0.0.1:1050,100
|
||||
--tx-proxy tor,socks5://username:password@127.0.0.1:1050,disable_noise
|
||||
--tx-proxy tor,socks5://username:password@127.0.0.1:1050,100,disable_noise
|
||||
--tx-proxy tor,[::1]:1050
|
||||
--tx-proxy tor,[::1]:1050,100
|
||||
--tx-proxy tor,[::1]:1050,disable_noise
|
||||
--tx-proxy tor,[::1]:1050,100,disable_noise
|
||||
--tx-proxy tor,socks5://[::1]:1050
|
||||
--tx-proxy tor,socks5://[::1]:1050,100
|
||||
--tx-proxy tor,socks5://[::1]:1050,disable_noise
|
||||
--tx-proxy tor,socks5://[::1]:1050,100,disable_noise
|
||||
--tx-proxy tor,socks5://username:password@[::1]:1050
|
||||
--tx-proxy tor,socks5://username:password@[::1]:1050,100
|
||||
--tx-proxy tor,socks5://username:password@[::1]:1050,disable_noise
|
||||
--tx-proxy tor,socks5://username:password@[::1]:1050,100,disable_noise
|
||||
--tx-proxy i2p,127.0.0.1:1050
|
||||
--tx-proxy i2p,127.0.0.1:1050,100
|
||||
--tx-proxy i2p,127.0.0.1:1050,disable_noise
|
||||
--tx-proxy i2p,127.0.0.1:1050,100,disable_noise
|
||||
--tx-proxy i2p,socks5://127.0.0.1:1050
|
||||
--tx-proxy i2p,socks5://127.0.0.1:1050,100
|
||||
--tx-proxy i2p,socks5://127.0.0.1:1050,disable_noise
|
||||
--tx-proxy i2p,socks5://127.0.0.1:1050,100,disable_noise
|
||||
--tx-proxy i2p,socks5://username:password@127.0.0.1:1050
|
||||
--tx-proxy i2p,socks5://username:password@127.0.0.1:1050,100
|
||||
--tx-proxy i2p,socks5://username:password@127.0.0.1:1050,disable_noise
|
||||
--tx-proxy i2p,socks5://username:password@127.0.0.1:1050,100,disable_noise
|
||||
--tx-proxy i2p,[::1]:1050
|
||||
--tx-proxy i2p,[::1]:1050,100
|
||||
--tx-proxy i2p,[::1]:1050,disable_noise
|
||||
--tx-proxy i2p,[::1]:1050,100,disable_noise
|
||||
--tx-proxy i2p,socks5://[::1]:1050
|
||||
--tx-proxy i2p,socks5://[::1]:1050,100
|
||||
--tx-proxy i2p,socks5://[::1]:1050,disable_noise
|
||||
--tx-proxy i2p,socks5://[::1]:1050,100,disable_noise
|
||||
--tx-proxy i2p,socks5://username:password@[::1]:1050
|
||||
--tx-proxy i2p,socks5://username:password@[::1]:1050,100
|
||||
--tx-proxy i2p,socks5://username:password@[::1]:1050,disable_noise
|
||||
--tx-proxy i2p,socks5://username:password@[::1]:1050,100,disable_noise
|
||||
```
|
||||
|
||||
The above examples are fairly exhaustive of all the possible option scenarios
|
||||
that will be incurred by the typical user.
|
||||
|
||||
#### The `network` portion of the option
|
||||
The first section (before the first `,`) indicates the network - only `tor` or
|
||||
`i2p` are valid here.
|
||||
|
||||
This portion of the option tells `--add-node`, `--add-priority-node`, and
|
||||
`--add-exclusive-node` options to use the specified proxy for those nodes. In
|
||||
other words, command-line specified hidden services are forwarded to their
|
||||
corresponding `--tx-proxy` server. Hidden services do **NOT** have to be
|
||||
specified on the command-line, there are built-in seed nodes for each network.
|
||||
|
||||
#### The `ip:port` portion of the option
|
||||
The second portion of the option (after the first `,` and _optionally_ ending
|
||||
in the next `,`) indicates the location of the socks server. The location
|
||||
**must** include an IPv4/IPv6 AND port. The location can optionally include the
|
||||
socks version - `socks4`, `socks4a`, and `socks5` are all valid here. If
|
||||
the socks version is not specified, `socks4a` is assumed.
|
||||
|
||||
An optional username and password can also be included. These fields support
|
||||
percent-encoding, see [wallet](#wallet) section for more information.
|
||||
|
||||
#### The last portion of the option
|
||||
After the ip:port section two options can be specified: the number of max
|
||||
connections and `disable_noise`. They can be specified in either order, but
|
||||
must be after the ip:port section.
|
||||
|
||||
The max connections does exactly as advertised, it limits the number of
|
||||
outgoing connections to the proxy. The `disable_noise` feature lowers the
|
||||
bandwidth requirements, and decreases the tx-relay time. When **NOT**
|
||||
specified, dummy P2P packets are sent periodically to connections (via the
|
||||
proxy) to conceal when a transaction is forwarded over the connection. When
|
||||
the option is specified, P2P links only send data for peerlist information and
|
||||
local outgoing transactions.
|
||||
|
||||
### `--anonymous-inbound`
|
||||
Currently the daemon cannot configure incoming hidden services connections.
|
||||
Instead, the user must manually configure Tor or I2P to accept inbound
|
||||
connections. Then, `--anonymous-inbound` must be used to tell the daemon where
|
||||
to listen for incoming connections, and the incoming hidden service address.
|
||||
The option can be specified once for each network type. The format for usage
|
||||
is: `hidden-service-address,[bind-ip:]port[,max_connections]`. Examples:
|
||||
|
||||
```
|
||||
--tx-proxy rveahdfho7wo4b2m.onion:18083,18083
|
||||
--tx-proxy rveahdfho7wo4b2m.onion:18083,18083,100
|
||||
--tx-proxy rveahdfho7wo4b2m.onion:18083,127.0.0.1:18083
|
||||
--tx-proxy rveahdfho7wo4b2m.onion:18083,127.0.0.1:18083,100
|
||||
--tx-proxy udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p,18083
|
||||
--tx-proxy udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p,18083,100
|
||||
--tx-proxy udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p,127.0.0.1:18083
|
||||
--tx-proxy udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p,127.0.0.1:18083,100
|
||||
```
|
||||
|
||||
Everything before the first `,` is the hidden service hostname. This must be
|
||||
a valid Tor or I2P address. This tells the daemon the **inbound** hidden
|
||||
service as configured for the local Tor or I2P daemons.
|
||||
|
||||
Everything between `,`s specify the bind ip and bind port. The IP address is
|
||||
optional, and defaults to `127.0.0.1`. The Tor and I2P daemons must be
|
||||
configured to forward incoming hidden service connections to this IP/Port pair.
|
||||
|
||||
Everything after the second `,` is used to specify the number of max inbound
|
||||
connections. The field is optional.
|
@ -94,7 +94,7 @@ namespace daemon_args
|
||||
|
||||
const command_line::arg_descriptor<std::string> arg_proxy = {
|
||||
"proxy",
|
||||
"Network communication through proxy: <socks-ip:port> i.e. \"127.0.0.1:9050\"",
|
||||
"Network communication through proxy: [socks5://[user:pass@]]<socks-ip:port> i.e. \"127.0.0.1:9050\"",
|
||||
"",
|
||||
};
|
||||
const command_line::arg_descriptor<bool> arg_proxy_allow_dns_leaks = {
|
||||
|
@ -54,6 +54,8 @@ namespace
|
||||
return "Failed to retrieve desired DNS record";
|
||||
case net::error::expected_tld:
|
||||
return "Expected top-level domain";
|
||||
case net::error::invalid_encoding:
|
||||
return "Invalid encoding";
|
||||
case net::error::invalid_host:
|
||||
return "Host value is not valid";
|
||||
case net::error::invalid_i2p_address:
|
||||
@ -62,8 +64,12 @@ namespace
|
||||
return "CIDR netmask outside of 0-32 range";
|
||||
case net::error::invalid_port:
|
||||
return "Invalid port value (expected 0-65535)";
|
||||
case net::error::invalid_scheme:
|
||||
return "Invalid/unsupported scheme was provided";
|
||||
case net::error::invalid_tor_address:
|
||||
return "Invalid Tor address";
|
||||
case net::error::unexpected_userinfo:
|
||||
return "User or pass was provided unexpectedly";
|
||||
case net::error::unsupported_address:
|
||||
return "Network address not supported";
|
||||
default:
|
||||
|
@ -41,11 +41,14 @@ namespace net
|
||||
bogus_dnssec = 1, //!< Invalid response signature from DNSSEC enabled domain
|
||||
dns_query_failure, //!< Failed to retrieve desired DNS record
|
||||
expected_tld, //!< Expected a tld
|
||||
invalid_encoding, //!< Invalid percent encoding
|
||||
invalid_host, //!< Hostname is not valid
|
||||
invalid_i2p_address,
|
||||
invalid_mask, //!< Outside of 0-32 range
|
||||
invalid_port, //!< Outside of 0-65535 range
|
||||
invalid_scheme, //!< Provided URI scheme was unspported
|
||||
invalid_tor_address,//!< Invalid base32 or length
|
||||
unexpected_userinfo,//!< User or pass was provided unexpectedly
|
||||
unsupported_address,//!< Type not supported by `get_network_address`
|
||||
|
||||
};
|
||||
|
@ -34,13 +34,19 @@
|
||||
namespace net
|
||||
{
|
||||
enum class error : int;
|
||||
struct scheme_and_authority;
|
||||
class tor_address;
|
||||
struct uri_components;
|
||||
struct user_and_pass;
|
||||
struct userinfo_and_hostport;
|
||||
class i2p_address;
|
||||
|
||||
namespace socks
|
||||
{
|
||||
class client;
|
||||
template<typename> class connect_handler;
|
||||
struct connector;
|
||||
struct endpoint;
|
||||
enum class error : int;
|
||||
enum class version : std::uint8_t;
|
||||
}
|
||||
|
@ -45,15 +45,17 @@ bool client::set_proxy(const std::string &address)
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto endpoint = get_tcp_endpoint(address);
|
||||
auto endpoint = socks::endpoint::get(address);
|
||||
if (!endpoint)
|
||||
{
|
||||
auto always_fail = net::socks::connector{boost::asio::ip::tcp::endpoint()};
|
||||
auto always_fail = net::socks::connector{};
|
||||
set_connector(always_fail);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_connector(net::socks::connector{*endpoint});
|
||||
set_connector(
|
||||
net::socks::connector{std::make_shared<socks::endpoint>(std::move(*endpoint))}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,9 @@
|
||||
|
||||
#include "parse.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include "hex.h"
|
||||
#include "net/socks.h"
|
||||
#include "net/tor_address.h"
|
||||
#include "net/i2p_address.h"
|
||||
#include "string_tools.h"
|
||||
@ -36,6 +39,95 @@
|
||||
|
||||
namespace net
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool percent_decoding(std::string& out)
|
||||
{
|
||||
auto pos = out.find('%');
|
||||
while (pos != std::string::npos)
|
||||
{
|
||||
if (out.size() - pos < 3)
|
||||
return false;
|
||||
if (!epee::from_hex::to_buffer({reinterpret_cast<std::uint8_t*>(out.data()) + pos, 1}, {out.data() + pos + 1, 2}))
|
||||
return false;
|
||||
out.erase(pos + 1, 2);
|
||||
pos = out.find('%', pos + 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // anonymous
|
||||
|
||||
scheme_and_authority::scheme_and_authority(boost::string_ref uri)
|
||||
: scheme(), authority()
|
||||
{
|
||||
static_assert(std::is_same<std::string::size_type, boost::string_ref::size_type>());
|
||||
|
||||
// Stop at scheme end or path begin. URN not supported
|
||||
const auto split = uri.find_first_of(":/");
|
||||
if (split != boost::string_ref::npos && uri.substr(split).starts_with("://"))
|
||||
{
|
||||
scheme.assign(uri.data(), split);
|
||||
uri = uri.substr(split + 3);
|
||||
}
|
||||
|
||||
uri = uri.substr(0, uri.find('/'));
|
||||
authority.assign(uri.data(), uri.size());
|
||||
}
|
||||
|
||||
userinfo_and_hostport::userinfo_and_hostport(boost::string_ref authority)
|
||||
: userinfo(), hostport()
|
||||
{
|
||||
static_assert(std::is_same<std::string::size_type, boost::string_ref::size_type>());
|
||||
|
||||
const auto split = authority.find('@');
|
||||
if (split != boost::string_ref::npos)
|
||||
{
|
||||
userinfo.assign(authority.data(), split);
|
||||
authority = authority.substr(split + 1);
|
||||
}
|
||||
|
||||
hostport.assign(authority.data(), authority.size());
|
||||
}
|
||||
|
||||
std::optional<user_and_pass> user_and_pass::get(boost::string_ref userinfo)
|
||||
{
|
||||
static_assert(std::is_same<std::string::size_type, boost::string_ref::size_type>());
|
||||
std::optional<user_and_pass> out{std::in_place};
|
||||
|
||||
const auto split = userinfo.find(':');
|
||||
if (split != boost::string_ref::npos)
|
||||
{
|
||||
out->user.assign(userinfo.data(), split);
|
||||
userinfo = userinfo.substr(split + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
out->user.assign(userinfo.data(), userinfo.size());
|
||||
userinfo = {};
|
||||
}
|
||||
|
||||
out->pass.assign(userinfo.data(), userinfo.size());
|
||||
if (percent_decoding(out->user) && percent_decoding(out->pass))
|
||||
return out;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<uri_components> uri_components::get(const boost::string_ref uri)
|
||||
{
|
||||
scheme_and_authority result1{uri};
|
||||
userinfo_and_hostport result2{result1.authority};
|
||||
auto result3 = user_and_pass::get(result2.userinfo);
|
||||
if (!result3)
|
||||
return std::nullopt;
|
||||
|
||||
std::optional<uri_components> out{std::in_place};
|
||||
out->scheme = std::move(result1.scheme);
|
||||
out->userinfo = std::move(*result3);
|
||||
out->hostport = std::move(result2.hostport);
|
||||
return out;
|
||||
}
|
||||
|
||||
void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port)
|
||||
{
|
||||
// If IPv6 address format with port "[addr:addr:addr:...:addr]:port"
|
||||
@ -104,7 +196,6 @@ namespace net
|
||||
if (epee::string_tools::get_ip_int32_from_string(ip, host_str))
|
||||
return {epee::net_utils::ipv4_network_address{ip, port}};
|
||||
}
|
||||
|
||||
return make_error_code(net::error::unsupported_address);
|
||||
}
|
||||
|
||||
@ -165,4 +256,46 @@ namespace net
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace socks
|
||||
{
|
||||
endpoint::endpoint()
|
||||
: endpoint(boost::asio::ip::tcp::endpoint{})
|
||||
{}
|
||||
|
||||
endpoint::endpoint(const boost::asio::ip::tcp::endpoint& address)
|
||||
: address(address), userinfo(), ver(version::v4a)
|
||||
{}
|
||||
|
||||
expect<endpoint> endpoint::get(const boost::string_ref uri)
|
||||
{
|
||||
auto components = uri_components::get(uri);
|
||||
if (!components)
|
||||
return {net::error::invalid_encoding};
|
||||
auto tcp_endpoint = get_tcp_endpoint(components->hostport);
|
||||
if (!tcp_endpoint)
|
||||
return tcp_endpoint.error();
|
||||
|
||||
endpoint out{};
|
||||
if (components->scheme.empty() || components->scheme == "socks" || components->scheme == "socks4a")
|
||||
out.ver = version::v4a;
|
||||
else if (components->scheme == "socks4")
|
||||
out.ver = version::v4;
|
||||
else if (components->scheme == "socks5")
|
||||
out.ver = version::v5;
|
||||
else
|
||||
return {net::error::invalid_scheme};
|
||||
|
||||
// Only version 5 supports user/pass authentication
|
||||
if (!components->userinfo.user.empty() || !components->userinfo.user.empty())
|
||||
{
|
||||
if (out.ver != version::v5)
|
||||
return {net::error::unexpected_userinfo};
|
||||
}
|
||||
|
||||
out.address = std::move(*tcp_endpoint);
|
||||
out.userinfo = std::move(components->userinfo);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,12 +32,73 @@
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/utility/string_ref.hpp>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "common/expect.h"
|
||||
#include "net/fwd.h"
|
||||
#include "net/net_utils_base.h"
|
||||
|
||||
namespace net
|
||||
{
|
||||
//! \brief Separates scheme, authority, and path sections of a URI.
|
||||
struct scheme_and_authority
|
||||
{
|
||||
//! \param uri with optional scheme, authority, and optional path. No URNs.
|
||||
explicit scheme_and_authority(boost::string_ref uri);
|
||||
|
||||
std::string scheme;
|
||||
std::string authority;
|
||||
};
|
||||
|
||||
//! \brief Separates the userinfo and host+port from URI authority.
|
||||
struct userinfo_and_hostport
|
||||
{
|
||||
//! \param authority portion of a URI.
|
||||
explicit userinfo_and_hostport(boost::string_ref authority);
|
||||
|
||||
std::string userinfo;
|
||||
std::string hostport;
|
||||
};
|
||||
|
||||
//! \brief Separates the user and pass sections from URI userinfo.
|
||||
struct user_and_pass
|
||||
{
|
||||
user_and_pass()
|
||||
: user(), pass()
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \param userinfo section of a URI.
|
||||
* \return User and pass with percent encoding removed. `std::nullopt`
|
||||
* if bad percent encoding
|
||||
*/
|
||||
static std::optional<user_and_pass> get(boost::string_ref userinfo);
|
||||
|
||||
std::string user;
|
||||
std::string pass;
|
||||
};
|
||||
|
||||
//! \brief Separates scheme, user, pass, and host+port sections of a URI.
|
||||
struct uri_components
|
||||
{
|
||||
uri_components()
|
||||
: scheme(), userinfo(), hostport()
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \param uri with optional scheme, optional user, optional pass,
|
||||
* authority, and optional path. URN not supported.
|
||||
* \return Scheme, user, pass, and host+port sections of a URI with
|
||||
* percent encoding removed on user and pass. `std::nullopt` if
|
||||
* bad percent encoding.
|
||||
*/
|
||||
static std::optional<uri_components> get(boost::string_ref uri);
|
||||
|
||||
std::string scheme;
|
||||
user_and_pass userinfo;
|
||||
std::string hostport;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Takes a valid address string (IP, Tor, I2P, or DNS name) and splits it into host and port
|
||||
*
|
||||
@ -79,5 +140,22 @@ namespace net
|
||||
get_ipv4_subnet_address(boost::string_ref address, bool allow_implicit_32 = false);
|
||||
|
||||
expect<boost::asio::ip::tcp::endpoint> get_tcp_endpoint(const boost::string_ref address);
|
||||
|
||||
namespace socks
|
||||
{
|
||||
//! \brief Separates TCP address, user+pass, and socks version
|
||||
struct endpoint
|
||||
{
|
||||
endpoint();
|
||||
explicit endpoint(const boost::asio::ip::tcp::endpoint& address);
|
||||
|
||||
//! \param uri with optional scheme, optional userinfo, and host+port.
|
||||
static expect<endpoint> get(boost::string_ref uri);
|
||||
|
||||
boost::asio::ip::tcp::endpoint address;
|
||||
user_and_pass userinfo;
|
||||
version ver;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,14 +30,17 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/coroutine.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 <numeric>
|
||||
#include <string>
|
||||
|
||||
#include "net/parse.h"
|
||||
#include "net/net_utils_base.h"
|
||||
#include "net/tor_address.h"
|
||||
#include "net/i2p_address.h"
|
||||
@ -52,6 +55,16 @@ namespace socks
|
||||
constexpr const std::uint8_t v4tor_resolve_command = 0xf0;
|
||||
constexpr const std::uint8_t v4_request_granted = 90;
|
||||
|
||||
constexpr const std::uint8_t v5_noauth_method = 0;
|
||||
constexpr const std::uint8_t v5_userpass_method = 2;
|
||||
constexpr const std::uint8_t v5_connect_command = 1;
|
||||
constexpr const std::uint8_t v5_reserved = 0;
|
||||
constexpr const std::uint8_t v5_ipv4_type = 1;
|
||||
constexpr const std::uint8_t v5_domain_type = 3;
|
||||
constexpr const std::uint8_t v5_ipv6_type = 4;
|
||||
constexpr const std::uint8_t v5_reply_success = 0;
|
||||
constexpr const std::uint8_t v5_userpass_version = 1;
|
||||
|
||||
struct v4_header
|
||||
{
|
||||
std::uint8_t version;
|
||||
@ -60,6 +73,114 @@ namespace socks
|
||||
boost::endian::big_uint32_t ip;
|
||||
};
|
||||
|
||||
struct v5_noauth_initial
|
||||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t n_methods;
|
||||
std::uint8_t method;
|
||||
|
||||
static constexpr v5_noauth_initial make() noexcept
|
||||
{
|
||||
return {5, 1, v5_noauth_method};
|
||||
}
|
||||
};
|
||||
|
||||
struct v5_auth_initial
|
||||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t n_methods;
|
||||
std::uint8_t method1;
|
||||
std::uint8_t method2;
|
||||
|
||||
static constexpr v5_auth_initial make() noexcept
|
||||
{
|
||||
return {5, 2, v5_noauth_method, v5_userpass_method};
|
||||
}
|
||||
};
|
||||
|
||||
struct v5_response_initial
|
||||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t method;
|
||||
};
|
||||
|
||||
struct v5_ipv4_connect
|
||||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t command;
|
||||
std::uint8_t reserved;
|
||||
std::uint8_t type;
|
||||
boost::endian::big_uint32_t ip;
|
||||
boost::endian::big_uint16_t port;
|
||||
|
||||
static v5_ipv4_connect make(const std::uint32_t ip, const std::uint16_t port) noexcept
|
||||
{
|
||||
return {5, v5_connect_command, v5_reserved, v5_ipv4_type, ip, port};
|
||||
}
|
||||
};
|
||||
|
||||
struct v5_domain_connect
|
||||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t command;
|
||||
std::uint8_t reserved;
|
||||
std::uint8_t type;
|
||||
std::uint8_t length;
|
||||
|
||||
static constexpr v5_domain_connect make(const std::uint8_t length) noexcept
|
||||
{
|
||||
return {5, v5_connect_command, v5_reserved, v5_domain_type, length};
|
||||
}
|
||||
};
|
||||
|
||||
struct v5_ipv6_connect
|
||||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t command;
|
||||
std::uint8_t reserved;
|
||||
std::uint8_t type;
|
||||
char ip[16];
|
||||
boost::endian::big_uint16_t port;
|
||||
|
||||
static v5_ipv6_connect make(const boost::asio::ip::address_v6& ip, const std::uint16_t port)
|
||||
{
|
||||
v5_ipv6_connect out{5, v5_connect_command, v5_reserved, v5_ipv6_type};
|
||||
out.port = port;
|
||||
|
||||
const auto ip_bytes = ip.to_bytes();
|
||||
static_assert(sizeof(out.ip) == sizeof(ip_bytes), "unexpected ipv6 bytes size");
|
||||
std::memcpy(std::addressof(out.ip), std::addressof(ip_bytes), sizeof(out.ip));
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
struct v5_response_auth
|
||||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t status;
|
||||
};
|
||||
|
||||
struct v5_response_connect
|
||||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t reply;
|
||||
std::uint8_t reserved;
|
||||
std::uint8_t type;
|
||||
};
|
||||
|
||||
struct v5_response_ipv4
|
||||
{
|
||||
boost::endian::big_uint32_t ip;
|
||||
boost::endian::big_uint16_t port;
|
||||
};
|
||||
|
||||
struct v5_response_ipv6
|
||||
{
|
||||
char ip[16];
|
||||
boost::endian::big_uint16_t port;
|
||||
};
|
||||
|
||||
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())
|
||||
@ -84,6 +205,131 @@ namespace socks
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
std::size_t write_v5_userpass(epee::span<std::uint8_t> out, const user_and_pass& userinfo)
|
||||
{
|
||||
static constexpr const std::uint8_t max_length = std::numeric_limits<std::uint8_t>::max();
|
||||
if (max_length < userinfo.user.size())
|
||||
return 0;
|
||||
if (max_length < userinfo.pass.size())
|
||||
return 0;
|
||||
|
||||
static_assert(max_length < std::numeric_limits<std::size_t>::max());
|
||||
static_assert(max_length < std::numeric_limits<std::size_t>::max() - max_length);
|
||||
static_assert(2 <= std::numeric_limits<std::size_t>::max() - max_length - max_length);
|
||||
|
||||
if (out.size() < 2 + userinfo.user.size() + userinfo.pass.size())
|
||||
return 0;
|
||||
|
||||
const std::size_t initial = out.size();
|
||||
|
||||
out[0] = v5_userpass_version;
|
||||
out[1] = std::uint8_t(userinfo.user.size());
|
||||
out.remove_prefix(2);
|
||||
|
||||
std::memcpy(out.data(), userinfo.user.data(), userinfo.user.size());
|
||||
out.remove_prefix(userinfo.user.size());
|
||||
|
||||
out[0] = std::uint8_t(userinfo.pass.size());
|
||||
out.remove_prefix(1);
|
||||
|
||||
std::memcpy(out.data(), userinfo.pass.data(), userinfo.pass.size());
|
||||
out.remove_prefix(userinfo.pass.size());
|
||||
return initial - out.size();
|
||||
}
|
||||
|
||||
std::array<std::uint16_t, 2> write_v5_initial(epee::span<std::uint8_t> out, const user_and_pass* userinfo)
|
||||
{
|
||||
std::array<std::uint16_t, 2> sizes{{}};
|
||||
|
||||
if (userinfo && (!userinfo->user.empty() || !userinfo->pass.empty()))
|
||||
{
|
||||
const auto header = v5_auth_initial::make();
|
||||
if (out.size() < sizeof(header))
|
||||
return sizes;
|
||||
std::memcpy(out.data(), std::addressof(header), sizeof(header));
|
||||
out.remove_prefix(sizeof(header));
|
||||
|
||||
const std::size_t auth = write_v5_userpass(out, *userinfo);
|
||||
if (!auth)
|
||||
return sizes;
|
||||
out.remove_prefix(auth);
|
||||
|
||||
std::get<0>(sizes) = sizeof(header);
|
||||
std::get<1>(sizes) = auth;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto header = v5_noauth_initial::make();
|
||||
if (out.size() < sizeof(header))
|
||||
return sizes;
|
||||
std::memcpy(out.data(), std::addressof(header), sizeof(header));
|
||||
out.remove_prefix(sizeof(header));
|
||||
|
||||
std::get<0>(sizes) = sizeof(header);
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::array<std::uint16_t, 3> write_v5_address_connect(epee::span<std::uint8_t> out, const T& address, const user_and_pass* userinfo)
|
||||
{
|
||||
std::array<std::uint16_t, 3> sizes{{}};
|
||||
|
||||
const auto result = write_v5_initial(out, userinfo);
|
||||
if (!std::get<0>(result))
|
||||
return sizes;
|
||||
|
||||
for (std::size_t length : result)
|
||||
out.remove_prefix(length);
|
||||
|
||||
if (out.size() < sizeof(address))
|
||||
return sizes;
|
||||
std::memcpy(out.data(), std::addressof(address), sizeof(address));
|
||||
|
||||
std::get<0>(sizes) = std::get<0>(result);
|
||||
std::get<1>(sizes) = std::get<1>(result);
|
||||
std::get<2>(sizes) = sizeof(address);
|
||||
return sizes;
|
||||
}
|
||||
|
||||
std::array<std::uint16_t, 3> write_v5_domain_connect(epee::span<std::uint8_t> out, const std::uint16_t port, const boost::string_ref domain, const user_and_pass* userinfo)
|
||||
{
|
||||
std::array<std::uint16_t, 3> sizes{{}};
|
||||
if (std::numeric_limits<std::uint8_t>::max() < domain.size())
|
||||
return sizes;
|
||||
|
||||
const auto result = write_v5_initial(out, userinfo);
|
||||
if (!std::get<0>(result))
|
||||
return sizes;
|
||||
|
||||
for (std::size_t length : result)
|
||||
out.remove_prefix(length);
|
||||
|
||||
const auto request = v5_domain_connect::make(std::uint8_t(domain.size()));
|
||||
static_assert(sizeof(port) <= std::numeric_limits<std::size_t>::max() - sizeof(request));
|
||||
if (std::numeric_limits<std::size_t>::max() - sizeof(request) - sizeof(port) < domain.size())
|
||||
return sizes;
|
||||
|
||||
const std::size_t last_size = sizeof(request) + sizeof(port) + domain.size();
|
||||
if (out.size() < last_size)
|
||||
return sizes;
|
||||
|
||||
std::memcpy(out.data(), std::addressof(request), sizeof(request));
|
||||
out.remove_prefix(sizeof(request));
|
||||
|
||||
std::memcpy(out.data(), domain.data(), domain.size());
|
||||
out.remove_prefix(domain.size());
|
||||
|
||||
const boost::endian::big_uint16_t big_port{port};
|
||||
std::memcpy(out.data(), std::addressof(big_port), sizeof(big_port));
|
||||
|
||||
std::get<0>(sizes) = std::get<0>(result);
|
||||
std::get<1>(sizes) = std::get<1>(result);
|
||||
std::get<2>(sizes) = last_size;
|
||||
return sizes;
|
||||
}
|
||||
|
||||
struct socks_category : boost::system::error_category
|
||||
{
|
||||
explicit socks_category() noexcept
|
||||
@ -99,6 +345,23 @@ namespace socks
|
||||
{
|
||||
switch (socks::error(value))
|
||||
{
|
||||
case socks::error::general_failure:
|
||||
return "Socks general server failure";
|
||||
case socks::error::not_allowed:
|
||||
return "Socks connection not allowed by ruleset";
|
||||
case socks::error::network_unreachable:
|
||||
return "Socks network unreachable";
|
||||
case socks::error::host_unreachable:
|
||||
return "Socks host unreachable";
|
||||
case socks::error::connection_refused:
|
||||
return "Socks connection refused";
|
||||
case socks::error::ttl_expired:
|
||||
return "Socks TTL expired";
|
||||
case socks::error::command_not_supported:
|
||||
return "Socks command not supported";
|
||||
case socks::error::address_type_not_supported:
|
||||
return "Socks address type not supported";
|
||||
|
||||
case socks::error::rejected:
|
||||
return "Socks request rejected or failed";
|
||||
case socks::error::identd_connection:
|
||||
@ -106,6 +369,8 @@ namespace socks
|
||||
case socks::error::identd_user:
|
||||
return "Socks request rejected because the client program and identd report different user-ids";
|
||||
|
||||
case socks::error::auth_failure:
|
||||
return "Socks authentication failure";
|
||||
case socks::error::bad_read:
|
||||
return "Socks boost::async_read read fewer bytes than expected";
|
||||
case socks::error::bad_write:
|
||||
@ -123,6 +388,10 @@ namespace socks
|
||||
{
|
||||
switch (socks::error(value))
|
||||
{
|
||||
case socks::error::network_unreachable:
|
||||
return boost::system::errc::host_unreachable;
|
||||
case socks::error::connection_refused:
|
||||
return boost::system::errc::connection_refused;
|
||||
case socks::error::bad_read:
|
||||
case socks::error::bad_write:
|
||||
return boost::system::errc::io_error;
|
||||
@ -156,18 +425,18 @@ namespace socks
|
||||
if (self_)
|
||||
{
|
||||
client& self = *self_;
|
||||
self.buffer_size_ = std::min(bytes, sizeof(self.buffer_));
|
||||
std::get<0>(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_));
|
||||
self.done(error, self_);
|
||||
else if (std::get<0>(self.buffer_size_) < sizeof(v4_header))
|
||||
self.done(socks::error::bad_read, self_);
|
||||
else if (self.buffer_[0] != 0) // response version
|
||||
self.done(socks::error::unexpected_version, std::move(self_));
|
||||
self.done(socks::error::unexpected_version, self_);
|
||||
else if (self.buffer_[1] != v4_request_granted)
|
||||
self.done(socks::error(int(self.buffer_[1]) + 1), std::move(self_));
|
||||
self.done(socks::error(int(self.buffer_[1]) + 1), self_);
|
||||
else
|
||||
self.done(boost::system::error_code{}, std::move(self_));
|
||||
self.done(boost::system::error_code{}, self_);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -179,6 +448,7 @@ namespace socks
|
||||
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");
|
||||
std::get<0>(self.buffer_size_) = sizeof(v4_header);
|
||||
return boost::asio::buffer(self.buffer_, sizeof(v4_header));
|
||||
}
|
||||
|
||||
@ -188,22 +458,188 @@ namespace socks
|
||||
{
|
||||
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_));
|
||||
self.done(error, self_);
|
||||
else if (bytes < std::get<0>(self.buffer_size_))
|
||||
self.done(socks::error::bad_write, self_);
|
||||
else
|
||||
boost::asio::async_read(self.proxy_, get_buffer(self), self.strand_.wrap(completed{std::move(self_)}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct client::process_v5 : boost::asio::coroutine
|
||||
{
|
||||
std::shared_ptr<client> self_;
|
||||
|
||||
struct read_bytes
|
||||
{
|
||||
std::uint8_t* data;
|
||||
std::size_t length;
|
||||
|
||||
boost::asio::mutable_buffers_1 to_asio() const noexcept
|
||||
{
|
||||
return boost::asio::buffer(data, length);
|
||||
}
|
||||
};
|
||||
|
||||
explicit process_v5(std::shared_ptr<client> self)
|
||||
: boost::asio::coroutine(), self_(std::move(self))
|
||||
{}
|
||||
|
||||
static read_bytes get_read_buffer(client& self, const std::size_t size)
|
||||
{
|
||||
const std::size_t offset =
|
||||
std::accumulate(self.buffer_size_.begin(), self.buffer_size_.end(), std::size_t(0));
|
||||
if (sizeof(self.buffer_) < offset || sizeof(self.buffer_) - offset < size)
|
||||
throw std::runtime_error{"Not enough room for reading socks v5 buffer"};
|
||||
return {self.buffer_ + offset, size};
|
||||
}
|
||||
|
||||
template<unsigned I>
|
||||
static boost::asio::const_buffers_1 get_write_buffer(const client& self) noexcept
|
||||
{
|
||||
const std::size_t offset =
|
||||
std::accumulate(self.buffer_size_.begin(), self.buffer_size_.begin() + I, std::size_t(0));
|
||||
return boost::asio::buffer(
|
||||
self.buffer_ + offset, std::get<I>(self.buffer_size_)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(const boost::system::error_code error, std::size_t bytes)
|
||||
{
|
||||
if (!self_)
|
||||
return;
|
||||
|
||||
client& self = *self_;
|
||||
if (error)
|
||||
{
|
||||
self.done(error, self_);
|
||||
return;
|
||||
}
|
||||
|
||||
bool send_userpass = false;
|
||||
BOOST_ASIO_CORO_REENTER(this)
|
||||
{
|
||||
// initial header already written
|
||||
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::async_read(
|
||||
self.proxy_,
|
||||
get_read_buffer(self, sizeof(v5_response_initial)).to_asio(),
|
||||
self.strand_.wrap(std::move(*this))
|
||||
);
|
||||
{
|
||||
v5_response_initial header{};
|
||||
|
||||
assert(bytes == sizeof(header));
|
||||
const auto buf = get_read_buffer(self, sizeof(header));
|
||||
std::memcpy(std::addressof(header), buf.data, sizeof(header));
|
||||
if (header.version != 5)
|
||||
{
|
||||
self.done(socks::error::unexpected_version, self_);
|
||||
return;
|
||||
}
|
||||
if (header.method != v5_noauth_method && header.method != v5_userpass_method)
|
||||
{
|
||||
self.done(socks::error::auth_failure, self_);
|
||||
return;
|
||||
}
|
||||
send_userpass = (header.method == v5_userpass_method);
|
||||
}
|
||||
|
||||
if (send_userpass)
|
||||
{
|
||||
if (!std::get<1>(self.buffer_size_))
|
||||
{
|
||||
self.done(socks::error::auth_failure, self_);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::async_write(
|
||||
self.proxy_, get_write_buffer<1>(self), self.strand_.wrap(std::move(*this))
|
||||
);
|
||||
assert(bytes == std::get<1>(self.buffer_size_));
|
||||
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::async_read(
|
||||
self.proxy_,
|
||||
get_read_buffer(self, sizeof(v5_response_auth)).to_asio(),
|
||||
self.strand_.wrap(std::move(*this))
|
||||
);
|
||||
{
|
||||
v5_response_auth header{};
|
||||
|
||||
assert(bytes == sizeof(header));
|
||||
const auto buf = get_read_buffer(self, sizeof(header));
|
||||
std::memcpy(std::addressof(header), buf.data, sizeof(header));
|
||||
if (header.version != v5_userpass_version)
|
||||
{
|
||||
self.done(socks::error::unexpected_version, self_);
|
||||
return;
|
||||
}
|
||||
if (header.status != v5_reply_success)
|
||||
{
|
||||
self.done(socks::error::auth_failure, self_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::async_write(
|
||||
self.proxy_, get_write_buffer<2>(self), self.strand_.wrap(std::move(*this))
|
||||
);
|
||||
assert(bytes == std::get<2>(self.buffer_size_));
|
||||
|
||||
self.buffer_size_ = {};
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::async_read(
|
||||
self.proxy_,
|
||||
get_read_buffer(self, sizeof(v5_response_connect)).to_asio(),
|
||||
self.strand_.wrap(std::move(*this))
|
||||
);
|
||||
{
|
||||
v5_response_connect header{};
|
||||
|
||||
assert(bytes == sizeof(header));
|
||||
const auto buf = get_read_buffer(self, sizeof(header));
|
||||
std::memcpy(std::addressof(header), buf.data, sizeof(header));
|
||||
if (header.version != 5)
|
||||
{
|
||||
self.done(socks::error::unexpected_version, self_);
|
||||
return;
|
||||
}
|
||||
if (header.reply != v5_reply_success)
|
||||
{
|
||||
self.done(socks::error(int(header.reply)), self_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.type == v5_ipv4_type)
|
||||
bytes = sizeof(v5_response_ipv4);
|
||||
else if (header.type == v5_ipv6_type)
|
||||
bytes = sizeof(v5_response_ipv6);
|
||||
else
|
||||
{
|
||||
self.done(socks::error::unexpected_version, self_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::get<0>(self.buffer_size_) = sizeof(v5_response_connect);
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::async_read(
|
||||
self.proxy_, get_read_buffer(self, bytes).to_asio(), self.strand_.wrap(std::move(*this))
|
||||
);
|
||||
std::get<0>(self.buffer_size_) =
|
||||
std::min(sizeof(self.buffer_), sizeof(v5_response_connect) + bytes);
|
||||
self.done(error, 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_);
|
||||
return boost::asio::buffer(self.buffer_, std::get<0>(self.buffer_size_));
|
||||
}
|
||||
|
||||
void operator()(const boost::system::error_code error)
|
||||
@ -212,20 +648,24 @@ namespace socks
|
||||
{
|
||||
client& self = *self_;
|
||||
if (error)
|
||||
self.done(error, std::move(self_));
|
||||
self.done(error, self_);
|
||||
else if (self.ver_ == version::v5)
|
||||
boost::asio::async_write(self.proxy_, get_buffer(self), self.strand_.wrap(process_v5{std::move(self_)}));
|
||||
else
|
||||
boost::asio::async_write(self.proxy_, get_buffer(self), self.strand_.wrap(read{std::move(self_)}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
client::client(stream_type::socket&& proxy, socks::version ver)
|
||||
: proxy_(std::move(proxy)), strand_(GET_IO_SERVICE(proxy_)), buffer_size_(0), buffer_(), ver_(ver)
|
||||
: proxy_(std::move(proxy)), strand_(GET_IO_SERVICE(proxy_)), buffer_size_{{}}, buffer_(), ver_(ver)
|
||||
{}
|
||||
|
||||
client::~client() {}
|
||||
|
||||
bool client::set_connect_command(const epee::net_utils::ipv4_network_address& address)
|
||||
bool client::set_connect_command(const epee::net_utils::ipv4_network_address& address, const user_and_pass* userinfo)
|
||||
{
|
||||
switch (socks_version())
|
||||
{
|
||||
@ -233,6 +673,13 @@ namespace socks
|
||||
case version::v4a:
|
||||
case version::v4a_tor:
|
||||
break;
|
||||
case version::v5:
|
||||
buffer_size_ = write_v5_address_connect(
|
||||
buffer_,
|
||||
v5_ipv4_connect::make(boost::endian::big_to_native(address.ip()), address.port()),
|
||||
userinfo
|
||||
);
|
||||
return std::get<0>(buffer_size_) != 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -240,29 +687,50 @@ namespace socks
|
||||
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");
|
||||
|
||||
if (userinfo && (!userinfo->user.empty() || !userinfo->pass.empty()))
|
||||
return false;
|
||||
|
||||
// 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;
|
||||
|
||||
buffer_size_ = {};
|
||||
std::get<0>(buffer_size_) = sizeof(temp) + 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool client::set_connect_command(const boost::string_ref domain, std::uint16_t port)
|
||||
bool client::set_connect_command(const epee::net_utils::ipv6_network_address& address, const user_and_pass* userinfo)
|
||||
{
|
||||
if (socks_version() != version::v5)
|
||||
return false;
|
||||
buffer_size_ = write_v5_address_connect(
|
||||
buffer_, v5_ipv6_connect::make(address.ip(), address.port()), userinfo
|
||||
);
|
||||
return std::get<0>(buffer_size_) != 0;
|
||||
}
|
||||
|
||||
bool client::set_connect_command(const boost::string_ref domain, std::uint16_t port, const user_and_pass* userinfo)
|
||||
{
|
||||
switch (socks_version())
|
||||
{
|
||||
case version::v4a:
|
||||
case version::v4a_tor:
|
||||
break;
|
||||
|
||||
case version::v5:
|
||||
buffer_size_ = write_v5_domain_connect(buffer_, port, domain, userinfo);
|
||||
return std::get<0>(buffer_size_) != 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (userinfo && (!userinfo->user.empty() || !userinfo->pass.empty()))
|
||||
return false;
|
||||
|
||||
const std::size_t buf_used = write_domain_header(buffer_, v4_connect_command, port, domain);
|
||||
buffer_size_ = buf_used;
|
||||
buffer_size_ = {};
|
||||
std::get<0>(buffer_size_) = buf_used;
|
||||
return buf_used != 0;
|
||||
}
|
||||
|
||||
@ -273,10 +741,10 @@ namespace socks
|
||||
return false;
|
||||
}
|
||||
|
||||
bool client::set_connect_command(const net::i2p_address& address)
|
||||
bool client::set_connect_command(const net::i2p_address& address, const user_and_pass* userinfo)
|
||||
{
|
||||
if (!address.is_unknown())
|
||||
return set_connect_command(address.host_str(), address.port());
|
||||
return set_connect_command(address.host_str(), address.port(), userinfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -286,13 +754,14 @@ namespace socks
|
||||
return false;
|
||||
|
||||
const std::size_t buf_used = write_domain_header(buffer_, v4tor_resolve_command, 0, domain);
|
||||
buffer_size_ = buf_used;
|
||||
buffer_size_ = {};
|
||||
std::get<0>(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())
|
||||
if (self && std::get<0>(self->buffer_size_))
|
||||
{
|
||||
client& alias = *self;
|
||||
alias.proxy_.async_connect(proxy_address, alias.strand_.wrap(write{std::move(self)}));
|
||||
@ -303,10 +772,13 @@ namespace socks
|
||||
|
||||
bool client::send(std::shared_ptr<client> self)
|
||||
{
|
||||
if (self && !self->buffer().empty())
|
||||
if (self && std::get<0>(self->buffer_size_))
|
||||
{
|
||||
client& alias = *self;
|
||||
boost::asio::async_write(alias.proxy_, write::get_buffer(alias), alias.strand_.wrap(read{std::move(self)}));
|
||||
if (alias.ver_ == version::v5)
|
||||
boost::asio::async_write(alias.proxy_, write::get_buffer(alias), alias.strand_.wrap(process_v5{std::move(self)}));
|
||||
else
|
||||
boost::asio::async_write(alias.proxy_, write::get_buffer(alias), alias.strand_.wrap(read{std::move(self)}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
@ -46,6 +47,7 @@ namespace epee
|
||||
namespace net_utils
|
||||
{
|
||||
class ipv4_network_address;
|
||||
class ipv6_network_address;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,19 +60,30 @@ namespace socks
|
||||
{
|
||||
v4 = 0,
|
||||
v4a,
|
||||
v4a_tor //!< Extensions defined in Tor codebase
|
||||
v4a_tor, //!< Extensions defined in Tor codebase
|
||||
v5
|
||||
};
|
||||
|
||||
//! 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).
|
||||
// v5 errors
|
||||
general_failure = 1,
|
||||
not_allowed,
|
||||
network_unreachable,
|
||||
host_unreachable,
|
||||
connection_refused,
|
||||
ttl_expired,
|
||||
command_not_supported,
|
||||
address_type_not_supported,
|
||||
// v4 errors
|
||||
rejected = 92,
|
||||
identd_connection,
|
||||
identd_user,
|
||||
// Specific to application
|
||||
bad_read = 257,
|
||||
auth_failure = 257,
|
||||
bad_read,
|
||||
bad_write,
|
||||
unexpected_version
|
||||
};
|
||||
@ -94,7 +107,7 @@ namespace socks
|
||||
{
|
||||
boost::asio::ip::tcp::socket proxy_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
std::uint16_t buffer_size_;
|
||||
std::array<std::uint16_t, 3> buffer_size_;
|
||||
std::uint8_t buffer_[1024];
|
||||
socks::version ver_;
|
||||
|
||||
@ -109,7 +122,7 @@ namespace socks
|
||||
\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;
|
||||
virtual void done(boost::system::error_code error, const std::shared_ptr<client>& self) = 0;
|
||||
|
||||
public:
|
||||
using stream_type = boost::asio::ip::tcp;
|
||||
@ -118,6 +131,7 @@ namespace socks
|
||||
struct write;
|
||||
struct read;
|
||||
struct completed;
|
||||
struct process_v5;
|
||||
|
||||
/*!
|
||||
\param proxy ownership is passed into `this`. Does not have to be
|
||||
@ -139,33 +153,45 @@ namespace socks
|
||||
//! \return Socks version.
|
||||
socks::version socks_version() const noexcept { return ver_; }
|
||||
|
||||
//! \return Contents of internal buffer.
|
||||
//! \return Contents of first internal buffer
|
||||
epee::span<const std::uint8_t> buffer() const noexcept
|
||||
{
|
||||
return {buffer_, buffer_size_};
|
||||
return {buffer_, std::get<0>(buffer_size_)};
|
||||
}
|
||||
|
||||
//! \post `buffer.empty()`.
|
||||
void clear_command() noexcept { buffer_size_ = 0; }
|
||||
//! \post `buffer_[0] = 0, buffer_[1] = 0`.
|
||||
void clear_command() noexcept { buffer_size_ = {}; }
|
||||
|
||||
//! Try to set `address` as remote connection request.
|
||||
bool set_connect_command(const epee::net_utils::ipv4_network_address& address);
|
||||
bool set_connect_command(
|
||||
const epee::net_utils::ipv4_network_address& address,
|
||||
const user_and_pass* userinfo = nullptr);
|
||||
|
||||
//! Try to set `address` as remote connection request.
|
||||
bool set_connect_command(
|
||||
const epee::net_utils::ipv6_network_address& address,
|
||||
const user_and_pass* userinfo = nullptr);
|
||||
|
||||
//! Try to set `domain` + `port` as remote connection request.
|
||||
bool set_connect_command(boost::string_ref domain, std::uint16_t port);
|
||||
bool set_connect_command(
|
||||
boost::string_ref domain,
|
||||
std::uint16_t port,
|
||||
const user_and_pass* userinfo = nullptr);
|
||||
|
||||
//! Try to set `address` as remote Tor hidden service connection request.
|
||||
bool set_connect_command(const net::tor_address& address);
|
||||
|
||||
//! Try to set `address` as remote i2p hidden service connection request.
|
||||
bool set_connect_command(const net::i2p_address& address);
|
||||
bool set_connect_command(
|
||||
const net::i2p_address& address,
|
||||
const user_and_pass* userinfo = nullptr);
|
||||
|
||||
//! 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
|
||||
Asynchronously connect to `proxy_address` then issue command(s) 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
|
||||
@ -181,7 +207,7 @@ namespace socks
|
||||
|
||||
/*!
|
||||
Assume existing connection to proxy server; asynchronously issue
|
||||
command in `buffer()`. The `done(...)` method will be invoked
|
||||
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
|
||||
@ -215,7 +241,7 @@ namespace socks
|
||||
{
|
||||
Handler handler_;
|
||||
|
||||
virtual void done(boost::system::error_code error, std::shared_ptr<client>) override
|
||||
virtual void done(boost::system::error_code error, const std::shared_ptr<client>&) override
|
||||
{
|
||||
handler_(error, take_socket());
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
#include "net/error.h"
|
||||
#include "net/net_utils_base.h"
|
||||
#include "net/parse.h"
|
||||
#include "net/socks.h"
|
||||
#include "string_tools.h"
|
||||
#include "string_tools_lexical.h"
|
||||
@ -44,9 +45,22 @@ namespace net
|
||||
{
|
||||
namespace socks
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool get_v6_address(boost::asio::ip::address_v6& out, const std::string& source)
|
||||
{
|
||||
boost::system::error_code error{};
|
||||
out = boost::asio::ip::address_v6::from_string(source, error);
|
||||
return !error;
|
||||
}
|
||||
} // anonymous
|
||||
|
||||
boost::unique_future<boost::asio::ip::tcp::socket>
|
||||
connector::operator()(const std::string& remote_host, const std::string& remote_port, boost::asio::steady_timer& timeout) const
|
||||
{
|
||||
if (!proxy_address)
|
||||
std::runtime_error{"Unexpected nullptr of net::socks::endpoint"};
|
||||
|
||||
struct future_socket
|
||||
{
|
||||
boost::promise<boost::asio::ip::tcp::socket> result_;
|
||||
@ -68,18 +82,21 @@ namespace socks
|
||||
|
||||
bool is_set = false;
|
||||
std::uint32_t ip_address = 0;
|
||||
boost::asio::ip::address_v6 v6_address{};
|
||||
boost::promise<boost::asio::ip::tcp::socket> result{};
|
||||
out = result.get_future();
|
||||
const auto proxy = net::socks::make_connect_client(
|
||||
boost::asio::ip::tcp::socket{GET_IO_SERVICE(timeout)}, net::socks::version::v4a, future_socket{std::move(result)}
|
||||
boost::asio::ip::tcp::socket{GET_IO_SERVICE(timeout)}, proxy_address->ver, future_socket{std::move(result)}
|
||||
);
|
||||
|
||||
if (epee::string_tools::get_ip_int32_from_string(ip_address, remote_host))
|
||||
is_set = proxy->set_connect_command(epee::net_utils::ipv4_network_address{ip_address, port});
|
||||
is_set = proxy->set_connect_command(epee::net_utils::ipv4_network_address{ip_address, port}, std::addressof(proxy_address->userinfo));
|
||||
else if (get_v6_address(v6_address, remote_host))
|
||||
is_set = proxy->set_connect_command(epee::net_utils::ipv6_network_address{v6_address, port}, std::addressof(proxy_address->userinfo));
|
||||
else
|
||||
is_set = proxy->set_connect_command(remote_host, port);
|
||||
is_set = proxy->set_connect_command(remote_host, port, std::addressof(proxy_address->userinfo));
|
||||
|
||||
if (!is_set || !net::socks::client::connect_and_send(proxy, proxy_address))
|
||||
if (!is_set || !net::socks::client::connect_and_send(proxy, proxy_address->address))
|
||||
throw std::system_error{net::error::invalid_host, "Address for socks proxy"};
|
||||
|
||||
timeout.async_wait(net::socks::client::async_close{std::move(proxy)});
|
||||
|
@ -31,8 +31,11 @@
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/thread/future.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "net/fwd.h"
|
||||
|
||||
namespace net
|
||||
{
|
||||
namespace socks
|
||||
@ -40,7 +43,7 @@ namespace socks
|
||||
//! Primarily for use with `epee::net_utils::http_client`.
|
||||
struct connector
|
||||
{
|
||||
boost::asio::ip::tcp::endpoint proxy_address;
|
||||
std::shared_ptr<endpoint> proxy_address;
|
||||
|
||||
/*! Creates a new socket, asynchronously connects to `proxy_address`,
|
||||
and requests a connection to `remote_host` on `remote_port`. Sets
|
||||
|
@ -81,7 +81,7 @@ namespace
|
||||
return {std::move(*address)};
|
||||
}
|
||||
|
||||
bool start_socks(std::shared_ptr<net::socks::client> client, const boost::asio::ip::tcp::endpoint& proxy, const epee::net_utils::network_address& remote)
|
||||
bool start_socks(std::shared_ptr<net::socks::client> client, const net::socks::endpoint& proxy, const epee::net_utils::network_address& remote)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(client != nullptr, false, "Unexpected null client");
|
||||
|
||||
@ -92,18 +92,25 @@ namespace
|
||||
set = client->set_connect_command(remote.as<net::tor_address>());
|
||||
break;
|
||||
case net::i2p_address::get_type_id():
|
||||
set = client->set_connect_command(remote.as<net::i2p_address>());
|
||||
set = client->set_connect_command(remote.as<net::i2p_address>(), std::addressof(proxy.userinfo));
|
||||
break;
|
||||
case epee::net_utils::ipv4_network_address::get_type_id():
|
||||
set = client->set_connect_command(remote.as<epee::net_utils::ipv4_network_address>());
|
||||
set = client->set_connect_command(remote.as<epee::net_utils::ipv4_network_address>(), std::addressof(proxy.userinfo));
|
||||
break;
|
||||
case epee::net_utils::ipv6_network_address::get_type_id():
|
||||
if (client->socks_version() == net::socks::version::v5)
|
||||
{
|
||||
set = client->set_connect_command(remote.as<epee::net_utils::ipv6_network_address>(), std::addressof(proxy.userinfo));
|
||||
break;
|
||||
}
|
||||
/* fallthrough */
|
||||
default:
|
||||
MERROR("Unsupported network address in socks_connect");
|
||||
MERROR("Unsupported network address in socks_connect. Try socks5://");
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool sent =
|
||||
set && net::socks::client::connect_and_send(std::move(client), proxy);
|
||||
set && net::socks::client::connect_and_send(std::move(client), proxy.address);
|
||||
CHECK_AND_ASSERT_MES(sent, false, "Unexpected failure to init socks client");
|
||||
return true;
|
||||
}
|
||||
@ -147,7 +154,7 @@ namespace nodetool
|
||||
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_exclusive_node = {"add-exclusive-node", "Specify list of peers to connect to only."
|
||||
" If this option is given the options add-priority-node and seed-node are ignored"};
|
||||
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_seed_node = {"seed-node", "Connect to a node to retrieve peer addresses, and disconnect"};
|
||||
const command_line::arg_descriptor<std::vector<std::string> > arg_tx_proxy = {"tx-proxy", "Send local txes through proxy: <network-type>,<socks-ip:port>[,max_connections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\""};
|
||||
const command_line::arg_descriptor<std::vector<std::string> > arg_tx_proxy = {"tx-proxy", "Send local txes through proxy: <network-type>,[socks5://[user:pass@]]<socks-ip:port>[,max_connections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\""};
|
||||
const command_line::arg_descriptor<std::vector<std::string> > arg_anonymous_inbound = {"anonymous-inbound", "<hidden-service-address>,<[bind-ip:]port>[,max_connections] i.e. \"x.onion,127.0.0.1:18083,100\""};
|
||||
const command_line::arg_descriptor<std::string> arg_ban_list = {"ban-list", "Specify ban list file, one IP address per line"};
|
||||
const command_line::arg_descriptor<bool> arg_p2p_hide_my_port = {"hide-my-port", "Do not announce yourself as peerlist candidate", false, true};
|
||||
@ -189,7 +196,7 @@ namespace nodetool
|
||||
const boost::string_ref zone{next->begin(), next->size()};
|
||||
|
||||
++next;
|
||||
CHECK_AND_ASSERT_MES(!next.eof() && !next->empty(), boost::none, "No ipv4:port given for --" << arg_tx_proxy.name);
|
||||
CHECK_AND_ASSERT_MES(!next.eof() && !next->empty(), boost::none, "No ip:port given for --" << arg_tx_proxy.name);
|
||||
const boost::string_ref proxy{next->begin(), next->size()};
|
||||
|
||||
++next;
|
||||
@ -227,14 +234,14 @@ namespace nodetool
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
std::uint32_t ip = 0;
|
||||
std::uint16_t port = 0;
|
||||
if (!epee::string_tools::parse_peer_from_string(ip, port, std::string{proxy}) || port == 0)
|
||||
auto endpoint = net::socks::endpoint::get(proxy);
|
||||
if (!endpoint)
|
||||
{
|
||||
MERROR("Invalid ipv4:port given for --" << arg_tx_proxy.name);
|
||||
MERROR("Invalid --" << arg_tx_proxy.name << " value: " << endpoint.error().message());
|
||||
return boost::none;
|
||||
}
|
||||
proxies.back().address = ip::tcp::endpoint{ip::address_v4{boost::endian::native_to_big(ip)}, port};
|
||||
|
||||
proxies.back().address = std::move(*endpoint);
|
||||
}
|
||||
|
||||
return proxies;
|
||||
@ -327,7 +334,7 @@ namespace nodetool
|
||||
}
|
||||
|
||||
boost::optional<boost::asio::ip::tcp::socket>
|
||||
socks_connect_internal(const std::atomic<bool>& stop_signal, boost::asio::io_service& service, const boost::asio::ip::tcp::endpoint& proxy, const epee::net_utils::network_address& remote)
|
||||
socks_connect_internal(const std::atomic<bool>& stop_signal, boost::asio::io_service& service, const net::socks::endpoint& proxy, const epee::net_utils::network_address& remote)
|
||||
{
|
||||
using socket_type = net::socks::client::stream_type::socket;
|
||||
using client_result = std::pair<boost::system::error_code, socket_type>;
|
||||
@ -349,7 +356,7 @@ namespace nodetool
|
||||
socks_result = socks_promise.get_future();
|
||||
|
||||
auto client = net::socks::make_connect_client(
|
||||
boost::asio::ip::tcp::socket{service}, net::socks::version::v4a, notify{std::move(socks_promise)}
|
||||
boost::asio::ip::tcp::socket{service}, proxy.ver, notify{std::move(socks_promise)}
|
||||
);
|
||||
close_client.self = client;
|
||||
if (!start_socks(std::move(client), proxy, remote))
|
||||
@ -361,7 +368,7 @@ namespace nodetool
|
||||
{
|
||||
if (socks_connect_timeout < std::chrono::steady_clock::now() - start)
|
||||
{
|
||||
MERROR("Timeout on socks connect (" << proxy << " to " << remote.str() << ")");
|
||||
MERROR("Timeout on socks connect (" << proxy.address << " to " << remote.str() << ")");
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
@ -378,7 +385,7 @@ namespace nodetool
|
||||
return {std::move(result.second)};
|
||||
}
|
||||
|
||||
MERROR("Failed to make socks connection to " << remote.str() << " (via " << proxy << "): " << result.first.message());
|
||||
MERROR("Failed to make socks connection to " << remote.str() << " (via " << proxy.address << "): " << result.first.message());
|
||||
}
|
||||
catch (boost::broken_promise const&)
|
||||
{}
|
||||
|
@ -55,7 +55,7 @@
|
||||
#include "math_helper.h"
|
||||
#include "net_node_common.h"
|
||||
#include "net/enums.h"
|
||||
#include "net/fwd.h"
|
||||
#include "net/parse.h"
|
||||
#include "common/command_line.h"
|
||||
|
||||
PUSH_WARNINGS
|
||||
@ -73,7 +73,7 @@ namespace nodetool
|
||||
{}
|
||||
|
||||
std::int64_t max_connections;
|
||||
boost::asio::ip::tcp::endpoint address;
|
||||
net::socks::endpoint address;
|
||||
epee::net_utils::zone zone;
|
||||
bool noise;
|
||||
};
|
||||
@ -103,7 +103,7 @@ namespace nodetool
|
||||
|
||||
// hides boost::future and chrono stuff from mondo template file
|
||||
boost::optional<boost::asio::ip::tcp::socket>
|
||||
socks_connect_internal(const std::atomic<bool>& stop_signal, boost::asio::io_service& service, const boost::asio::ip::tcp::endpoint& proxy, const epee::net_utils::network_address& remote);
|
||||
socks_connect_internal(const std::atomic<bool>& stop_signal, boost::asio::io_service& service, const net::socks::endpoint& proxy, const epee::net_utils::network_address& remote);
|
||||
|
||||
|
||||
template<class base_type>
|
||||
@ -212,7 +212,7 @@ namespace nodetool
|
||||
epee::net_utils::network_address m_our_address; // in anonymity networks
|
||||
peerlist_manager m_peerlist;
|
||||
config m_config;
|
||||
boost::asio::ip::tcp::endpoint m_proxy_address;
|
||||
net::socks::endpoint m_proxy_address;
|
||||
std::atomic<unsigned int> m_current_number_of_out_peers;
|
||||
std::atomic<unsigned int> m_current_number_of_in_peers;
|
||||
boost::shared_mutex m_seed_nodes_lock;
|
||||
|
@ -900,8 +900,8 @@ namespace nodetool
|
||||
CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line");
|
||||
if (proxy.size())
|
||||
{
|
||||
const auto endpoint = net::get_tcp_endpoint(proxy);
|
||||
CHECK_AND_ASSERT_MES(endpoint, false, "Failed to parse proxy: " << proxy << " - " << endpoint.error());
|
||||
const auto endpoint = net::socks::endpoint::get(proxy);
|
||||
CHECK_AND_ASSERT_MES(endpoint, false, "Failed to parse proxy: " << proxy << " - " << endpoint.error().message());
|
||||
network_zone& public_zone = m_network_zones[epee::net_utils::zone::public_];
|
||||
public_zone.m_connect = &socks_connect;
|
||||
public_zone.m_proxy_address = *endpoint;
|
||||
|
@ -435,7 +435,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
|
||||
{
|
||||
proxy = command_line::get_arg(vm, opts.proxy);
|
||||
THROW_WALLET_EXCEPTION_IF(
|
||||
!net::get_tcp_endpoint(proxy),
|
||||
!net::socks::endpoint::get(proxy),
|
||||
tools::error::wallet_internal_error,
|
||||
std::string{"Invalid address specified for --"} + opts.proxy.name);
|
||||
}
|
||||
|
@ -883,6 +883,231 @@ TEST(get_network_address_host_and_port, hostname)
|
||||
na_host_and_port_test("xmrchain.net:18081", "xmrchain.net", "18081");
|
||||
}
|
||||
|
||||
TEST(scheme_and_authority, basic)
|
||||
{
|
||||
const auto check = [] (const net::scheme_and_authority& actual, const boost::string_ref scheme, const boost::string_ref authority)
|
||||
{
|
||||
EXPECT_EQ(actual.scheme, scheme);
|
||||
EXPECT_EQ(actual.authority, authority);
|
||||
};
|
||||
|
||||
// valid (some ipv6 hostnames are non-standard but allowed)
|
||||
check(net::scheme_and_authority{"socks://host:port/path"}, "socks", "host:port");
|
||||
check(net::scheme_and_authority{"socks://[::ffff]:8080/path"}, "socks", "[::ffff]:8080");
|
||||
check(net::scheme_and_authority{"socks://192.168.0.1/path"}, "socks", "192.168.0.1");
|
||||
check(net::scheme_and_authority{"socks://host"}, "socks", "host");
|
||||
check(net::scheme_and_authority{"socks://@host"}, "socks", "@host");
|
||||
check(net::scheme_and_authority{"socks://user:pass@host"}, "socks", "user:pass@host");
|
||||
check(net::scheme_and_authority{"host:port/path"}, "", "host:port");
|
||||
check(net::scheme_and_authority{"[::ffff]:8080/path"}, "", "[::ffff]:8080");
|
||||
check(net::scheme_and_authority{"192.168.0.1/path"}, "", "192.168.0.1");
|
||||
check(net::scheme_and_authority{"host"}, "", "host");
|
||||
check(net::scheme_and_authority{"192.168.0.1"}, "", "192.168.0.1");
|
||||
check(net::scheme_and_authority{"192.168.0.1:80/path"}, "", "192.168.0.1:80");
|
||||
check(net::scheme_and_authority{"::ffff"}, "", "::ffff");
|
||||
check(net::scheme_and_authority{"[::ffff]:8080"}, "", "[::ffff]:8080");
|
||||
check(net::scheme_and_authority{"example.com/some://valid/path"}, "", "example.com");
|
||||
|
||||
// unsupported URIs (URN cases)
|
||||
check(net::scheme_and_authority{"urn:isbn:number"}, "", "urn:isbn:number");
|
||||
check(net::scheme_and_authority{"urn:isbn/number"}, "", "urn:isbn");
|
||||
|
||||
// invalid cases _not_ strictly rejected until hostname is parsed fully
|
||||
check(net::scheme_and_authority{""}, "", "");
|
||||
check(net::scheme_and_authority{"socks://"}, "socks", "");
|
||||
check(net::scheme_and_authority{"socks:/"}, "", "socks:");
|
||||
check(net::scheme_and_authority{"192.168.0.1:80://"}, "", "192.168.0.1:80:");
|
||||
check(net::scheme_and_authority{"user@::ffff:443"}, "", "user@::ffff:443");
|
||||
check(net::scheme_and_authority{"socks://user:p%61ss@ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:8080"}, "socks", "user:p%61ss@ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:8080");
|
||||
}
|
||||
|
||||
|
||||
TEST(userinfo_and_hostport, basic)
|
||||
{
|
||||
const auto check = [] (const net::userinfo_and_hostport& actual, const boost::string_ref userinfo, const boost::string_ref hostport)
|
||||
{
|
||||
EXPECT_EQ(actual.userinfo, userinfo);
|
||||
EXPECT_EQ(actual.hostport, hostport);
|
||||
};
|
||||
|
||||
check(net::userinfo_and_hostport{"@host:port"}, "", "host:port");
|
||||
check(net::userinfo_and_hostport{"host:port"}, "", "host:port");
|
||||
check(net::userinfo_and_hostport{"[::ffff]"}, "", "[::ffff]");
|
||||
check(net::userinfo_and_hostport{"::ffff"}, "", "::ffff");
|
||||
check(net::userinfo_and_hostport{"user@[::ffff]"}, "user", "[::ffff]");
|
||||
check(net::userinfo_and_hostport{"user:%70ass@192.168.0.1"}, "user:%70ass", "192.168.0.1");
|
||||
check(net::userinfo_and_hostport{"user:pass@host:8080"}, "user:pass", "host:8080");
|
||||
|
||||
// invalid hostname not strictly rejected
|
||||
check(net::userinfo_and_hostport{""}, "", "");
|
||||
check(net::userinfo_and_hostport{"@"}, "", "");
|
||||
check(net::userinfo_and_hostport{":@"}, ":", "");
|
||||
check(net::userinfo_and_hostport{"user@::ffff:443"}, "user", "::ffff:443");
|
||||
}
|
||||
|
||||
TEST(user_and_pass, basic)
|
||||
{
|
||||
const auto check = [] (
|
||||
const std::optional<net::user_and_pass>& actual,
|
||||
const std::optional<std::string>& user = std::nullopt,
|
||||
const boost::string_ref pass = "")
|
||||
{
|
||||
ASSERT_EQ(bool(actual), bool(user));
|
||||
if (actual)
|
||||
{
|
||||
EXPECT_EQ(actual->user, *user);
|
||||
EXPECT_EQ(actual->pass, pass);
|
||||
}
|
||||
};
|
||||
|
||||
check(net::user_and_pass::get(""), std::string{""}, "");
|
||||
check(net::user_and_pass::get("user"), std::string{"user"}, "");
|
||||
check(net::user_and_pass::get("user:"), std::string{"user"}, "");
|
||||
check(net::user_and_pass::get(":pass"), std::string{}, "pass");
|
||||
check(net::user_and_pass::get("user:pass"), std::string("user"), "pass");
|
||||
check(net::user_and_pass::get("user:p%3Ass"), std::string("user"), "p:ss");
|
||||
check(net::user_and_pass::get("%2fser:"), std::string{"/ser"}, "");
|
||||
check(net::user_and_pass::get("user:pas%21"), std::string{"user"}, "pas!");
|
||||
check(net::user_and_pass::get("user::pass"), std::string{"user"}, ":pass");
|
||||
check(net::user_and_pass::get("user%3A:pass"), std::string{"user:"}, "pass");
|
||||
check(net::user_and_pass::get("%25%3A:pass"), std::string{"%:"}, "pass");
|
||||
check(net::user_and_pass::get("user:%00%FF"), std::string{"user"}, boost::string_ref{"\x00\xFF", 2});
|
||||
|
||||
// invalid percent encodings
|
||||
check(net::user_and_pass::get("user%3T"));
|
||||
check(net::user_and_pass::get("user%T"));
|
||||
check(net::user_and_pass::get("user%3"));
|
||||
check(net::user_and_pass::get("user%"));
|
||||
}
|
||||
|
||||
TEST(uri_components, get)
|
||||
{
|
||||
const auto check = [] (
|
||||
const std::optional<net::uri_components>& actual,
|
||||
const std::optional<std::string>& scheme = std::nullopt,
|
||||
const boost::string_ref user = "",
|
||||
const boost::string_ref pass = "",
|
||||
const boost::string_ref hostport = "")
|
||||
{
|
||||
ASSERT_EQ(bool(actual), bool(scheme));
|
||||
if (actual)
|
||||
{
|
||||
EXPECT_EQ(actual->scheme, *scheme);
|
||||
EXPECT_EQ(actual->userinfo.user, user);
|
||||
EXPECT_EQ(actual->userinfo.pass, pass);
|
||||
EXPECT_EQ(actual->hostport, hostport);
|
||||
}
|
||||
};
|
||||
|
||||
// valid (some ipv6 hostnames are non-standard but allowed)
|
||||
check(net::uri_components::get("socks://host:port/path"), std::string{"socks"}, "", "", "host:port");
|
||||
check(net::uri_components::get("socks://[::ffff]:8080/path"), std::string{"socks"}, "", "", "[::ffff]:8080");
|
||||
check(net::uri_components::get("socks://192.168.0.1/path"), "socks", "", "", "192.168.0.1");
|
||||
check(net::uri_components::get("socks://host"), "socks", "", "", "host");
|
||||
check(net::uri_components::get("socks://@host"), std::string{"socks"}, "", "", "host");
|
||||
check(net::uri_components::get("socks://:@host"), std::string{"socks"}, "", "", "host");
|
||||
check(net::uri_components::get("socks://user:@host"), std::string{"socks"}, "user", "", "host");
|
||||
check(net::uri_components::get("socks://:pass@host"), std::string{"socks"}, "", "pass", "host");
|
||||
check(net::uri_components::get("socks://user:pass@host"), std::string{"socks"}, "user", "pass", "host");
|
||||
check(net::uri_components::get("host:port/path"), "", "", "", "host:port");
|
||||
check(net::uri_components::get("[::ffff]:8080/path"), "", "", "", "[::ffff]:8080");
|
||||
check(net::uri_components::get("192.168.0.1/path"), "", "", "", "192.168.0.1");
|
||||
check(net::uri_components::get("host"), "", "", "", "host");
|
||||
check(net::uri_components::get("192.168.0.1"), "", "", "", "192.168.0.1");
|
||||
check(net::uri_components::get("192.168.0.1:80/path"), "", "", "", "192.168.0.1:80");
|
||||
check(net::uri_components::get("::ffff"), std::string{}, "", "", "::ffff");
|
||||
check(net::uri_components::get("[::ffff]:8080"), std::string{}, "", "", "[::ffff]:8080");
|
||||
check(net::uri_components::get("example.com/some://valid/path"), "", "", "", "example.com");
|
||||
|
||||
// unsupported URIs (URN cases)
|
||||
check(net::uri_components::get("urn:isbn:number"), std::string{}, "", "", "urn:isbn:number");
|
||||
check(net::uri_components::get("urn:isbn/number"), std::string{}, "", "", "urn:isbn");
|
||||
|
||||
// invalid cases _not_ strictly rejected until hostname is parsed fully
|
||||
check(net::uri_components::get(""), std::string{}, "", "", "");
|
||||
check(net::uri_components::get("socks://"), std::string{"socks"}, "", "", "");
|
||||
check(net::uri_components::get("socks:/"), std::string{}, "", "", "socks:");
|
||||
check(net::uri_components::get("192.168.0.1:80://"), std::string{}, "", "", "192.168.0.1:80:");
|
||||
check(net::uri_components::get("user@::ffff:443"), std::string{}, "user", "", "::ffff:443");
|
||||
check(net::uri_components::get("socks://user:p%61ss@ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:8080"), std::string{"socks"}, "user", "pass", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:8080");
|
||||
|
||||
// invalid percent encodings
|
||||
check(net::uri_components::get("scheme://user%3T@host"));
|
||||
check(net::uri_components::get("user%T@host"));
|
||||
check(net::uri_components::get("scheme://user:%3pass@host"));
|
||||
check(net::uri_components::get("user%@host"));
|
||||
}
|
||||
|
||||
TEST(socks_endpoint, get)
|
||||
{
|
||||
using v4_address = boost::asio::ip::address_v4;
|
||||
using v6_address = boost::asio::ip::address_v6;
|
||||
const auto check = [] (
|
||||
const expect<net::socks::endpoint>& actual,
|
||||
const std::optional<boost::asio::ip::address>& address = std::nullopt,
|
||||
const std::uint16_t port = 0,
|
||||
const boost::string_ref user = "",
|
||||
const boost::string_ref pass = "",
|
||||
const net::socks::version ver = net::socks::version::v4a)
|
||||
{
|
||||
ASSERT_EQ(bool(actual), bool(address)) << actual.error().message();
|
||||
if (actual)
|
||||
{
|
||||
EXPECT_EQ(actual->address.address(), address);
|
||||
EXPECT_EQ(actual->address.port(), port);
|
||||
EXPECT_EQ(actual->userinfo.user, user);
|
||||
EXPECT_EQ(actual->userinfo.pass, pass);
|
||||
EXPECT_EQ(actual->ver, ver);
|
||||
}
|
||||
};
|
||||
|
||||
check(net::socks::endpoint::get("socks://[::ffff]:8080/path"), v6_address::from_string("::ffff"), 8080, "", "", net::socks::version::v4a);
|
||||
check(net::socks::endpoint::get("socks5://user:%70ass@[::ffff]:8080/path"), v6_address::from_string("::ffff"), 8080, "user", "pass", net::socks::version::v5);
|
||||
check(net::socks::endpoint::get("socks4a://192.168.0.1:1/path"), v4_address::from_string("192.168.0.1"), 1, "", "", net::socks::version::v4a);
|
||||
check(net::socks::endpoint::get("socks5://%75@192.168.0.1:1/path"), v4_address::from_string("192.168.0.1"), 1, "u", "", net::socks::version::v5);
|
||||
check(net::socks::endpoint::get("[::ffff]:8080/path"), v6_address::from_string("::ffff"), 8080, "", "", net::socks::version::v4a);
|
||||
check(net::socks::endpoint::get("192.168.0.1:50"), v4_address::from_string("192.168.0.1"), 50, "", "", net::socks::version::v4a);
|
||||
check(net::socks::endpoint::get("192.168.0.1:80/path"), v4_address::from_string("192.168.0.1"), 80, "", "", net::socks::version::v4a);
|
||||
|
||||
// URNs should be rejected
|
||||
check(net::socks::endpoint::get("urn:isbn:number"));
|
||||
check(net::socks::endpoint::get("urn:isbn/number"));
|
||||
|
||||
// port required for socks
|
||||
check(net::socks::endpoint::get("socks5://192.168.0.1/path"));
|
||||
check(net::socks::endpoint::get("192.168.0.1/path"));
|
||||
check(net::socks::endpoint::get("::ffff"));
|
||||
|
||||
// invalid for socks - hostnames not allowed
|
||||
check(net::socks::endpoint::get("socks://host:/path"));
|
||||
check(net::socks::endpoint::get("socks://host:1"));
|
||||
check(net::socks::endpoint::get("socks://@host:1"));
|
||||
check(net::socks::endpoint::get("socks://:@host:1"));
|
||||
check(net::socks::endpoint::get("socks://user:@host:1"));
|
||||
check(net::socks::endpoint::get("socks://:pass@host:1"));
|
||||
check(net::socks::endpoint::get("socks://user:pass@host:1"));
|
||||
check(net::socks::endpoint::get("host:1"));
|
||||
check(net::socks::endpoint::get("host:1/path"));
|
||||
check(net::socks::endpoint::get("example.com:1/some://valid/path"));
|
||||
|
||||
// invalid cases rejected - more bad hostnames
|
||||
check(net::socks::endpoint::get(""));
|
||||
check(net::socks::endpoint::get("socks://"));
|
||||
check(net::socks::endpoint::get("socks:/"));
|
||||
check(net::socks::endpoint::get("192.168.0.1:80://"));
|
||||
check(net::socks::endpoint::get("::ffff:443"));
|
||||
check(net::socks::endpoint::get("socks5://user:p%61ss@ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:8080"));
|
||||
|
||||
// invalid percent encodings
|
||||
check(net::socks::endpoint::get("sock5://user%3T@127.0.0.1:1"));
|
||||
check(net::socks::endpoint::get("socks5::user%T@127.0.0.1:1"));
|
||||
check(net::socks::endpoint::get("socks5://user:%3pass@127.0.0.1:1"));
|
||||
check(net::socks::endpoint::get("socks5://user%@127.0.0.1:1"));
|
||||
|
||||
// user+pass requires socks5
|
||||
check(net::socks::endpoint::get("socks://user:pass@[::ffff]:8080"));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using stream_type = boost::asio::ip::tcp;
|
||||
@ -952,6 +1177,58 @@ TEST(socks_client, unsupported_command)
|
||||
|
||||
EXPECT_FALSE(test_client->set_resolve_command("example.com"));
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
EXPECT_FALSE(test_client->set_connect_command(epee::net_utils::ipv6_network_address{}));
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
test_client = net::socks::make_connect_client(
|
||||
stream_type::socket{io_service}, net::socks::version::v5, std::bind( [] {} )
|
||||
);
|
||||
ASSERT_TRUE(bool(test_client));
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
net::user_and_pass userinfo{};
|
||||
userinfo.user = std::string(256, 'a');
|
||||
|
||||
EXPECT_FALSE(test_client->set_connect_command(userinfo.user, 8080));
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
EXPECT_FALSE(test_client->set_connect_command("a", 8080, std::addressof(userinfo)));
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
EXPECT_FALSE(
|
||||
test_client->set_connect_command(
|
||||
epee::net_utils::ipv4_network_address{}, std::addressof(userinfo)
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
EXPECT_FALSE(
|
||||
test_client->set_connect_command(
|
||||
epee::net_utils::ipv6_network_address{}, std::addressof(userinfo)
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
userinfo.pass = std::move(userinfo.user);
|
||||
userinfo.user.clear();
|
||||
|
||||
EXPECT_FALSE(test_client->set_connect_command("a", 8080, std::addressof(userinfo)));
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
EXPECT_FALSE(
|
||||
test_client->set_connect_command(
|
||||
epee::net_utils::ipv4_network_address{}, std::addressof(userinfo)
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
|
||||
EXPECT_FALSE(
|
||||
test_client->set_connect_command(
|
||||
epee::net_utils::ipv6_network_address{}, std::addressof(userinfo)
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(test_client->buffer().empty());
|
||||
}
|
||||
|
||||
TEST(socks_client, no_command)
|
||||
@ -999,6 +1276,62 @@ TEST(socks_client, connect_command)
|
||||
while (!called);
|
||||
}
|
||||
|
||||
TEST(socks_client, v5_ipv6_connect_command)
|
||||
{
|
||||
io_thread io{};
|
||||
stream_type::socket client{io.io_service};
|
||||
|
||||
const boost::asio::ip::address_v6::bytes_type address{0xDE, 0xAD, 0xBE, 0xEF};
|
||||
|
||||
std::atomic<bool> called{false};
|
||||
auto test_client = net::socks::make_connect_client(
|
||||
std::move(client), net::socks::version::v5, checked_client{std::addressof(called), false}
|
||||
);
|
||||
ASSERT_TRUE(bool(test_client));
|
||||
|
||||
ASSERT_TRUE(
|
||||
test_client->set_connect_command(
|
||||
epee::net_utils::ipv6_network_address{boost::asio::ip::address_v6{address}, 80}
|
||||
)
|
||||
);
|
||||
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)
|
||||
ASSERT_FALSE(called);
|
||||
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {5, 1, 0};
|
||||
|
||||
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[] = {5, 0};
|
||||
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||
}
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {
|
||||
5, 1, 0, 4, 0xDE, 0xAD, 0xBE, 0xEF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0x50
|
||||
};
|
||||
|
||||
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[] = {
|
||||
5, 0, 0, 4, 0xBE, 0xEF, 0xDE, 0xAD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0x50
|
||||
};
|
||||
|
||||
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||
}
|
||||
|
||||
// yikes!
|
||||
while (!called);
|
||||
}
|
||||
|
||||
|
||||
TEST(socks_client, connect_command_failed)
|
||||
{
|
||||
io_thread io{};
|
||||
@ -1035,6 +1368,56 @@ TEST(socks_client, connect_command_failed)
|
||||
while (!called);
|
||||
}
|
||||
|
||||
|
||||
TEST(socks_client, v5_ipv4_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::v5, 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)), 80}
|
||||
)
|
||||
);
|
||||
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)
|
||||
ASSERT_FALSE(called);
|
||||
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {5, 1, 0};
|
||||
|
||||
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[] = {5, 0};
|
||||
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||
}
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {
|
||||
5, 1, 0, 1, 0, 0, 0x13, 0x88, 0, 0x50
|
||||
};
|
||||
|
||||
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[] = {5, 2, 0, 1, 0, 0, 0, 0, 0, 0x50};
|
||||
|
||||
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};
|
||||
@ -1050,7 +1433,7 @@ TEST(socks_client, resolve_command)
|
||||
, expected_(false)
|
||||
{};
|
||||
|
||||
virtual void done(boost::system::error_code error, std::shared_ptr<client> self) override
|
||||
virtual void done(boost::system::error_code error, const std::shared_ptr<client>& self) override
|
||||
{
|
||||
EXPECT_EQ(this, self.get());
|
||||
EXPECT_EQ(expected_, bool(error)) << "Resolve failure: " << error.message();
|
||||
@ -1106,6 +1489,120 @@ TEST(socks_client, resolve_command)
|
||||
while (test_client->called_ == 1);
|
||||
}
|
||||
|
||||
TEST(socks_client, v5_username_host_connect)
|
||||
{
|
||||
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::v5, checked_client{std::addressof(called), false}
|
||||
);
|
||||
ASSERT_TRUE(bool(test_client));
|
||||
|
||||
const auto userinfo =
|
||||
net::user_and_pass::get("user:pass").value_or(net::user_and_pass{});
|
||||
ASSERT_TRUE(
|
||||
test_client->set_connect_command("example.com", 80, std::addressof(userinfo))
|
||||
);
|
||||
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)
|
||||
ASSERT_FALSE(called);
|
||||
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {5, 2, 0, 2};
|
||||
|
||||
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[] = {5, 2};
|
||||
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||
}
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {
|
||||
1, 4, 'u', 's', 'e', 'r', 4, 'p', 'a', 's', 's'
|
||||
};
|
||||
|
||||
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[] = {1, 0};
|
||||
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||
}
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {
|
||||
5, 1, 0, 3, 11, 'e','x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o',
|
||||
'm', 0, 0x50
|
||||
};
|
||||
|
||||
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[] = {
|
||||
5, 0, 0, 1, 0xDE, 0xAD, 0xBE, 0xEF, 0x50, 00
|
||||
};
|
||||
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||
}
|
||||
|
||||
// yikes!
|
||||
while (!called);
|
||||
}
|
||||
|
||||
TEST(socks_client, v5_usernameskipped_host_connect)
|
||||
{
|
||||
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::v5, checked_client{std::addressof(called), false}
|
||||
);
|
||||
ASSERT_TRUE(bool(test_client));
|
||||
|
||||
const auto userinfo =
|
||||
net::user_and_pass::get("user:pass").value_or(net::user_and_pass{});
|
||||
ASSERT_TRUE(
|
||||
test_client->set_connect_command("example.com", 80, std::addressof(userinfo))
|
||||
);
|
||||
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)
|
||||
ASSERT_FALSE(called);
|
||||
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {5, 2, 0, 2};
|
||||
|
||||
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[] = {5, 0};
|
||||
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||
}
|
||||
{
|
||||
const std::uint8_t expected_bytes[] = {
|
||||
5, 1, 0, 3, 11, 'e','x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o',
|
||||
'm', 0, 0x50
|
||||
};
|
||||
|
||||
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[] = {
|
||||
5, 0, 0, 1, 0xDE, 0xAD, 0xBE, 0xEF, 0x50, 00
|
||||
};
|
||||
boost::asio::write(io.server, boost::asio::buffer(reply_bytes));
|
||||
}
|
||||
|
||||
// yikes!
|
||||
while (!called);
|
||||
}
|
||||
|
||||
TEST(socks_connector, host)
|
||||
{
|
||||
io_thread io{};
|
||||
@ -1113,7 +1610,9 @@ TEST(socks_connector, host)
|
||||
timeout.expires_from_now(std::chrono::seconds{5});
|
||||
|
||||
boost::unique_future<boost::asio::ip::tcp::socket> sock =
|
||||
net::socks::connector{io.acceptor.local_endpoint()}("example.com", "8080", timeout);
|
||||
net::socks::connector{
|
||||
std::make_shared<net::socks::endpoint>(io.acceptor.local_endpoint())
|
||||
}("example.com", "8080", timeout);
|
||||
|
||||
while (!io.connected)
|
||||
ASSERT_FALSE(sock.is_ready());
|
||||
@ -1140,7 +1639,9 @@ TEST(socks_connector, ipv4)
|
||||
timeout.expires_from_now(std::chrono::seconds{5});
|
||||
|
||||
boost::unique_future<boost::asio::ip::tcp::socket> sock =
|
||||
net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout);
|
||||
net::socks::connector{
|
||||
std::make_shared<net::socks::endpoint>(io.acceptor.local_endpoint())
|
||||
}("250.88.125.99", "8080", timeout);
|
||||
|
||||
while (!io.connected)
|
||||
ASSERT_FALSE(sock.is_ready());
|
||||
@ -1166,7 +1667,9 @@ TEST(socks_connector, error)
|
||||
timeout.expires_from_now(std::chrono::seconds{5});
|
||||
|
||||
boost::unique_future<boost::asio::ip::tcp::socket> sock =
|
||||
net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout);
|
||||
net::socks::connector{
|
||||
std::make_shared<net::socks::endpoint>(io.acceptor.local_endpoint())
|
||||
}("250.88.125.99", "8080", timeout);
|
||||
|
||||
while (!io.connected)
|
||||
ASSERT_FALSE(sock.is_ready());
|
||||
@ -1192,7 +1695,9 @@ TEST(socks_connector, timeout)
|
||||
timeout.expires_from_now(std::chrono::milliseconds{10});
|
||||
|
||||
boost::unique_future<boost::asio::ip::tcp::socket> sock =
|
||||
net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout);
|
||||
net::socks::connector{
|
||||
std::make_shared<net::socks::endpoint>(io.acceptor.local_endpoint())
|
||||
}("250.88.125.99", "8080", timeout);
|
||||
|
||||
ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3}));
|
||||
EXPECT_THROW(sock.get().is_open(), boost::system::system_error);
|
||||
|
Loading…
Reference in New Issue
Block a user