Merge bitcoin/bitcoin#27375: net: support unix domain sockets for -proxy and -onion

567cec9a05 doc: add release notes and help text for unix sockets (Matthew Zipkin)
bfe5192891 test: cover UNIX sockets in feature_proxy.py (Matthew Zipkin)
c65c0d0163 init: allow UNIX socket path for -proxy and -onion (Matthew Zipkin)
c3bd43142e gui: accomodate unix socket Proxy in updateDefaultProxyNets() (Matthew Zipkin)
a88bf9dedd i2p: construct Session with Proxy instead of CService (Matthew Zipkin)
d9318a37ec net: split ConnectToSocket() from ConnectDirectly() for unix sockets (Matthew Zipkin)
ac2ecf3182 proxy: rename randomize_credentials to m_randomize_credentials (Matthew Zipkin)
a89c3f59dc netbase: extend Proxy class to wrap UNIX socket as well as TCP (Matthew Zipkin)
3a7d6548ef net: move CreateSock() calls from ConnectNode() to netbase methods (Matthew Zipkin)
74f568cb6f netbase: allow CreateSock() to create UNIX sockets if supported (Matthew Zipkin)
bae86c8d31 netbase: refactor CreateSock() to accept sa_family_t (Matthew Zipkin)
adb3a3e51d configure: test for unix domain sockets (Matthew Zipkin)

Pull request description:

  Closes https://github.com/bitcoin/bitcoin/issues/27252

  UNIX domain sockets are a mechanism for inter-process communication that are faster than local TCP ports (because there is no need for TCP overhead) and potentially more secure because access is managed by the filesystem instead of serving an open port on the system.

  There has been work on [unix domain sockets before](https://github.com/bitcoin/bitcoin/pull/9979) but for now I just wanted to start on this single use-case which is enabling unix sockets from the client side, specifically connecting to a local Tor proxy (Tor can listen on unix sockets and even enforces strict curent-user-only access permission before binding) configured by `-onion=` or `-proxy=`

  I copied the prefix `unix:` usage from Tor. With this patch built locally you can test with your own filesystem path (example):

  `tor --SocksPort unix:/Users/matthewzipkin/torsocket/x`

  `bitcoind -proxy=unix:/Users/matthewzipkin/torsocket/x`

  Prep work for this feature includes:
  - Moving where and how we create `sockaddr` and `Sock` to accommodate `AF_UNIX` without disturbing `CService`
  - Expanding `Proxy` class to represent either a `CService` or a UNIX socket (by its file path)

  Future work:
  - Enable UNIX sockets for ZMQ (https://github.com/bitcoin/bitcoin/pull/27679)
  - Enable UNIX sockets for I2P SAM proxy (some code is included in this PR but not tested or exposed to user options yet)
  - Enable UNIX sockets on windows where supported
  - Update Network Proxies dialog in GUI to support UNIX sockets

ACKs for top commit:
  Sjors:
    re-ACK 567cec9a05
  tdb3:
    re ACK for 567cec9a05.
  achow101:
    ACK 567cec9a05
  vasild:
    ACK 567cec9a05

Tree-SHA512: de81860e56d5de83217a18df4c35297732b4ad491e293a0153d2d02a0bde1d022700a1131279b187ef219651487537354b9d06d10fde56225500c7e257df92c1
pull/29496/head
Ava Chow 2 months ago
commit 0ed2c130e7
No known key found for this signature in database
GPG Key ID: 17565732E08E5E41

@ -1198,10 +1198,23 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
]], [[
getauxval(AT_HWCAP);
]])],
[ AC_MSG_RESULT([yes]); HAVE_STRONG_GETAUXVAL=1; AC_DEFINE([HAVE_STRONG_GETAUXVAL], [1], [Define this symbol to build code that uses getauxval)]) ],
[ AC_MSG_RESULT([yes]); HAVE_STRONG_GETAUXVAL=1; AC_DEFINE([HAVE_STRONG_GETAUXVAL], [1], [Define this symbol to build code that uses getauxval]) ],
[ AC_MSG_RESULT([no]); HAVE_STRONG_GETAUXVAL=0 ]
)
# Check for UNIX sockets
AC_MSG_CHECKING(for sockaddr_un)
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <sys/socket.h>
#include <sys/un.h>
]], [[
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
]])],
[ AC_MSG_RESULT([yes]); AC_DEFINE([HAVE_SOCKADDR_UN], [1], [Define this symbol if platform supports unix domain sockets]) ],
[ AC_MSG_RESULT([no]); ]
)
have_any_system=no
AC_MSG_CHECKING([for std::system])
AC_LINK_IFELSE(

@ -0,0 +1,6 @@
P2P
---
UNIX domain sockets can now be used for proxy connections. Set `-onion` or `-proxy`
to the local socket path with the prefix `unix:` (e.g. `-onion=unix:/home/me/torsocket`).
(#27375)

@ -32,6 +32,13 @@
#include <unistd.h> // IWYU pragma: export
#endif
// Windows does not have `sa_family_t` - it defines `sockaddr::sa_family` as `u_short`.
// Thus define `sa_family_t` on Windows too so that the rest of the code can use `sa_family_t`.
// See https://learn.microsoft.com/en-us/windows/win32/api/winsock/ns-winsock-sockaddr#syntax
#ifdef WIN32
typedef u_short sa_family_t;
#endif
// We map Linux / BSD error functions and codes, to the equivalent
// Windows definitions, and use the WSA* names throughout our code.
// Note that glibc defines EWOULDBLOCK as EAGAIN (see errno.h).

@ -115,7 +115,7 @@ static CNetAddr DestB64ToAddr(const std::string& dest)
namespace sam {
Session::Session(const fs::path& private_key_file,
const CService& control_host,
const Proxy& control_host,
CThreadInterrupt* interrupt)
: m_private_key_file{private_key_file},
m_control_host{control_host},
@ -124,7 +124,7 @@ Session::Session(const fs::path& private_key_file,
{
}
Session::Session(const CService& control_host, CThreadInterrupt* interrupt)
Session::Session(const Proxy& control_host, CThreadInterrupt* interrupt)
: m_control_host{control_host},
m_interrupt{interrupt},
m_transient{true}
@ -327,14 +327,10 @@ Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
std::unique_ptr<Sock> Session::Hello() const
{
auto sock = CreateSock(m_control_host);
auto sock = m_control_host.Connect();
if (!sock) {
throw std::runtime_error("Cannot create socket");
}
if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout, true)) {
throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToStringAddrPort()));
throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));
}
SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1");
@ -418,7 +414,7 @@ void Session::CreateIfNotCreatedAlready()
const auto session_type = m_transient ? "transient" : "persistent";
const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs
Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToStringAddrPort());
Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToString());
auto sock = Hello();

@ -7,6 +7,7 @@
#include <compat/compat.h>
#include <netaddress.h>
#include <netbase.h>
#include <sync.h>
#include <util/fs.h>
#include <util/sock.h>
@ -67,7 +68,7 @@ public:
* `Session` object.
*/
Session(const fs::path& private_key_file,
const CService& control_host,
const Proxy& control_host,
CThreadInterrupt* interrupt);
/**
@ -81,7 +82,7 @@ public:
* `CThreadInterrupt` object is saved, so it must not be destroyed earlier than this
* `Session` object.
*/
Session(const CService& control_host, CThreadInterrupt* interrupt);
Session(const Proxy& control_host, CThreadInterrupt* interrupt);
/**
* Destroy the session, closing the internally used sockets. The sockets that have been
@ -235,9 +236,9 @@ private:
const fs::path m_private_key_file;
/**
* The host and port of the SAM control service.
* The SAM control service proxy.
*/
const CService m_control_host;
const Proxy m_control_host;
/**
* Cease network activity when this is signaled.

@ -534,7 +534,11 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection memory usage for the send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by outbound peers forward or backward by this amount (default: %u seconds).", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target per 24h. Limit does not apply to peers with 'download' permission or blocks created within past week. 0 = no limit (default: %s). Optional suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000 base while uppercase is 1024 base", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#if HAVE_SOCKADDR_UN
argsman.AddArg("-onion=<ip:port|path>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy). May be a local file path prefixed with 'unix:'.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#else
argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#endif
argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-onlynet=<net>", "Make automatic outbound connections only to network <net> (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@ -545,7 +549,11 @@ void SetupServerArgs(ArgsManager& argsman)
// TODO: remove the sentence "Nodes not using ... incoming connections." once the changes from
// https://github.com/bitcoin/bitcoin/pull/23542 have become widespread.
argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
#if HAVE_SOCKADDR_UN
argsman.AddArg("-proxy=<ip:port|path>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled). May be a local file path prefixed with 'unix:' if the proxy supports it.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION);
#else
argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION);
#endif
argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@ -1323,7 +1331,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
std::string host_out;
uint16_t port_out{0};
if (!SplitHostPort(socket_addr, port_out, host_out)) {
#if HAVE_SOCKADDR_UN
// Allow unix domain sockets for -proxy and -onion e.g. unix:/some/file/path
if ((port_option != "-proxy" && port_option != "-onion") || socket_addr.find(ADDR_PREFIX_UNIX) != 0) {
return InitError(InvalidPortErrMsg(port_option, socket_addr));
}
#else
return InitError(InvalidPortErrMsg(port_option, socket_addr));
#endif
}
}
}
@ -1390,12 +1405,18 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default
std::string proxyArg = args.GetArg("-proxy", "");
if (proxyArg != "" && proxyArg != "0") {
const std::optional<CService> proxyAddr{Lookup(proxyArg, 9050, fNameLookup)};
if (!proxyAddr.has_value()) {
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
Proxy addrProxy;
if (IsUnixSocketPath(proxyArg)) {
addrProxy = Proxy(proxyArg, proxyRandomize);
} else {
const std::optional<CService> proxyAddr{Lookup(proxyArg, 9050, fNameLookup)};
if (!proxyAddr.has_value()) {
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
}
addrProxy = Proxy(proxyAddr.value(), proxyRandomize);
}
Proxy addrProxy = Proxy(proxyAddr.value(), proxyRandomize);
if (!addrProxy.IsValid())
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
@ -1421,11 +1442,16 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
"reaching the Tor network is explicitly forbidden: -onion=0"));
}
} else {
const std::optional<CService> addr{Lookup(onionArg, 9050, fNameLookup)};
if (!addr.has_value() || !addr->IsValid()) {
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
if (IsUnixSocketPath(onionArg)) {
onion_proxy = Proxy(onionArg, proxyRandomize);
} else {
const std::optional<CService> addr{Lookup(onionArg, 9050, fNameLookup)};
if (!addr.has_value() || !addr->IsValid()) {
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
}
onion_proxy = Proxy(addr.value(), proxyRandomize);
}
onion_proxy = Proxy{addr.value(), proxyRandomize};
}
}

@ -437,7 +437,6 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
}
// Connect
bool connected = false;
std::unique_ptr<Sock> sock;
Proxy proxy;
CAddress addr_bind;
@ -450,6 +449,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
if (addrConnect.IsI2P() && use_proxy) {
i2p::Connection conn;
bool connected{false};
if (m_i2p_sam_session) {
connected = m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed);
@ -458,7 +458,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
LOCK(m_unused_i2p_sessions_mutex);
if (m_unused_i2p_sessions.empty()) {
i2p_transient_session =
std::make_unique<i2p::sam::Session>(proxy.proxy, &interruptNet);
std::make_unique<i2p::sam::Session>(proxy, &interruptNet);
} else {
i2p_transient_session.swap(m_unused_i2p_sessions.front());
m_unused_i2p_sessions.pop();
@ -478,20 +478,11 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
addr_bind = CAddress{conn.me, NODE_NONE};
}
} else if (use_proxy) {
sock = CreateSock(proxy.proxy);
if (!sock) {
return nullptr;
}
connected = ConnectThroughProxy(proxy, addrConnect.ToStringAddr(), addrConnect.GetPort(),
*sock, nConnectTimeout, proxyConnectionFailed);
LogPrintLevel(BCLog::PROXY, BCLog::Level::Debug, "Using proxy: %s to connect to %s:%s\n", proxy.ToString(), addrConnect.ToStringAddr(), addrConnect.GetPort());
sock = ConnectThroughProxy(proxy, addrConnect.ToStringAddr(), addrConnect.GetPort(), proxyConnectionFailed);
} else {
// no proxy needed (none set for target network)
sock = CreateSock(addrConnect);
if (!sock) {
return nullptr;
}
connected = ConnectSocketDirectly(addrConnect, *sock, nConnectTimeout,
conn_type == ConnectionType::MANUAL);
sock = ConnectDirectly(addrConnect, conn_type == ConnectionType::MANUAL);
}
if (!proxyConnectionFailed) {
// If a connection to the node was attempted, and failure (if any) is not caused by a problem connecting to
@ -499,18 +490,13 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
addrman.Attempt(addrConnect, fCountFailure);
}
} else if (pszDest && GetNameProxy(proxy)) {
sock = CreateSock(proxy.proxy);
if (!sock) {
return nullptr;
}
std::string host;
uint16_t port{default_port};
SplitHostPort(std::string(pszDest), port, host);
bool proxyConnectionFailed;
connected = ConnectThroughProxy(proxy, host, port, *sock, nConnectTimeout,
proxyConnectionFailed);
sock = ConnectThroughProxy(proxy, host, port, proxyConnectionFailed);
}
if (!connected) {
if (!sock) {
return nullptr;
}
@ -2990,7 +2976,7 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError,
return false;
}
std::unique_ptr<Sock> sock = CreateSock(addrBind);
std::unique_ptr<Sock> sock = CreateSock(addrBind.GetSAFamily());
if (!sock) {
strError = strprintf(Untranslated("Couldn't open socket for incoming connections (socket returned error %s)"), NetworkErrorString(WSAGetLastError()));
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original);
@ -3197,7 +3183,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
Proxy i2p_sam;
if (GetProxy(NET_I2P, i2p_sam) && connOptions.m_i2p_accept_incoming) {
m_i2p_sam_session = std::make_unique<i2p::sam::Session>(gArgs.GetDataDirNet() / "i2p_private_key",
i2p_sam.proxy, &interruptNet);
i2p_sam, &interruptNet);
}
for (const auto& strDest : connOptions.vSeedNodes) {

@ -818,6 +818,19 @@ bool CService::SetSockAddr(const struct sockaddr *paddr)
}
}
sa_family_t CService::GetSAFamily() const
{
switch (m_net) {
case NET_IPV4:
return AF_INET;
case NET_IPV6:
case NET_CJDNS:
return AF_INET6;
default:
return AF_UNSPEC;
}
}
uint16_t CService::GetPort() const
{
return port;

@ -540,6 +540,11 @@ public:
uint16_t GetPort() const;
bool GetSockAddr(struct sockaddr* paddr, socklen_t* addrlen) const;
bool SetSockAddr(const struct sockaddr* paddr);
/**
* Get the address family
* @returns AF_UNSPEC if unspecified
*/
[[nodiscard]] sa_family_t GetSAFamily() const;
friend bool operator==(const CService& a, const CService& b);
friend bool operator!=(const CService& a, const CService& b) { return !(a == b); }
friend bool operator<(const CService& a, const CService& b);

@ -3,6 +3,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <netbase.h>
#include <compat/compat.h>
@ -21,6 +25,10 @@
#include <limits>
#include <memory>
#if HAVE_SOCKADDR_UN
#include <sys/un.h>
#endif
// Settings
static GlobalMutex g_proxyinfo_mutex;
static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex);
@ -208,6 +216,24 @@ CService LookupNumeric(const std::string& name, uint16_t portDefault, DNSLookupF
return Lookup(name, portDefault, /*fAllowLookup=*/false, dns_lookup_function).value_or(CService{});
}
bool IsUnixSocketPath(const std::string& name)
{
#if HAVE_SOCKADDR_UN
if (name.find(ADDR_PREFIX_UNIX) != 0) return false;
// Split off "unix:" prefix
std::string str{name.substr(ADDR_PREFIX_UNIX.length())};
// Path size limit is platform-dependent
// see https://manpages.ubuntu.com/manpages/xenial/en/man7/unix.7.html
if (str.size() + 1 > sizeof(((sockaddr_un*)nullptr)->sun_path)) return false;
return true;
#else
return false;
#endif
}
/** SOCKS version */
enum SOCKSVersion: uint8_t {
SOCKS4 = 0x04,
@ -461,18 +487,18 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
}
}
std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
std::unique_ptr<Sock> CreateSockOS(sa_family_t address_family)
{
// Create a sockaddr from the specified service.
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
if (!address_family.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
LogPrintf("Cannot create socket for %s: unsupported network\n", address_family.ToStringAddrPort());
return nullptr;
}
// Not IPv4, IPv6 or UNIX
if (address_family == AF_UNSPEC) return nullptr;
int protocol{IPPROTO_TCP};
#if HAVE_SOCKADDR_UN
if (address_family == AF_UNIX) protocol = 0;
#endif
// Create a TCP socket in the address family of the specified service.
SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
// Create a socket in the specified address family.
SOCKET hSocket = socket(address_family, SOCK_STREAM, protocol);
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
@ -496,21 +522,25 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
}
#endif
// Set the no-delay option (disable Nagle's algorithm) on the TCP socket.
const int on{1};
if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) {
LogPrint(BCLog::NET, "Unable to set TCP_NODELAY on a newly created socket, continuing anyway\n");
}
// Set the non-blocking option on the socket.
if (!sock->SetNonBlocking()) {
LogPrintf("Error setting socket to non-blocking: %s\n", NetworkErrorString(WSAGetLastError()));
return nullptr;
}
#if HAVE_SOCKADDR_UN
if (address_family == AF_UNIX) return sock;
#endif
// Set the no-delay option (disable Nagle's algorithm) on the TCP socket.
const int on{1};
if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) {
LogPrint(BCLog::NET, "Unable to set TCP_NODELAY on a newly created socket, continuing anyway\n");
}
return sock;
}
std::function<std::unique_ptr<Sock>(const CService&)> CreateSock = CreateSockTCP;
std::function<std::unique_ptr<Sock>(const sa_family_t&)> CreateSock = CreateSockOS;
template<typename... Args>
static void LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args) {
@ -522,18 +552,10 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg
}
}
bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nTimeout, bool manual_connection)
static bool ConnectToSocket(const Sock& sock, struct sockaddr* sockaddr, socklen_t len, const std::string& dest_str, bool manual_connection)
{
// Create a sockaddr from the specified service.
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToStringAddrPort());
return false;
}
// Connect to the addrConnect service on the hSocket socket.
if (sock.Connect(reinterpret_cast<struct sockaddr*>(&sockaddr), len) == SOCKET_ERROR) {
// Connect to `sockaddr` using `sock`.
if (sock.Connect(sockaddr, len) == SOCKET_ERROR) {
int nErr = WSAGetLastError();
// WSAEINVAL is here because some legacy version of winsock uses it
if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL)
@ -543,13 +565,13 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT
// synchronously to check for successful connection with a timeout.
const Sock::Event requested = Sock::RECV | Sock::SEND;
Sock::Event occurred;
if (!sock.Wait(std::chrono::milliseconds{nTimeout}, requested, &occurred)) {
if (!sock.Wait(std::chrono::milliseconds{nConnectTimeout}, requested, &occurred)) {
LogPrintf("wait for connect to %s failed: %s\n",
addrConnect.ToStringAddrPort(),
dest_str,
NetworkErrorString(WSAGetLastError()));
return false;
} else if (occurred == 0) {
LogPrint(BCLog::NET, "connection attempt to %s timed out\n", addrConnect.ToStringAddrPort());
LogPrint(BCLog::NET, "connection attempt to %s timed out\n", dest_str);
return false;
}
@ -561,13 +583,13 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT
socklen_t sockerr_len = sizeof(sockerr);
if (sock.GetSockOpt(SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&sockerr, &sockerr_len) ==
SOCKET_ERROR) {
LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToStringAddrPort(), NetworkErrorString(WSAGetLastError()));
LogPrintf("getsockopt() for %s failed: %s\n", dest_str, NetworkErrorString(WSAGetLastError()));
return false;
}
if (sockerr != 0) {
LogConnectFailure(manual_connection,
"connect() to %s failed after wait: %s",
addrConnect.ToStringAddrPort(),
dest_str,
NetworkErrorString(sockerr));
return false;
}
@ -578,13 +600,73 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT
else
#endif
{
LogConnectFailure(manual_connection, "connect() to %s failed: %s", addrConnect.ToStringAddrPort(), NetworkErrorString(WSAGetLastError()));
LogConnectFailure(manual_connection, "connect() to %s failed: %s", dest_str, NetworkErrorString(WSAGetLastError()));
return false;
}
}
return true;
}
std::unique_ptr<Sock> ConnectDirectly(const CService& dest, bool manual_connection)
{
auto sock = CreateSock(dest.GetSAFamily());
if (!sock) {
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Cannot create a socket for connecting to %s\n", dest.ToStringAddrPort());
return {};
}
// Create a sockaddr from the specified service.
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
if (!dest.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
LogPrintf("Cannot get sockaddr for %s: unsupported network\n", dest.ToStringAddrPort());
return {};
}
if (!ConnectToSocket(*sock, (struct sockaddr*)&sockaddr, len, dest.ToStringAddrPort(), manual_connection)) {
LogPrintf("Cannot connect to socket for %s\n", dest.ToStringAddrPort());
return {};
}
return sock;
}
std::unique_ptr<Sock> Proxy::Connect() const
{
if (!IsValid()) {
LogPrintf("Cannot connect to invalid Proxy\n");
return {};
}
if (!m_is_unix_socket) return ConnectDirectly(proxy, /*manual_connection=*/true);
#if HAVE_SOCKADDR_UN
auto sock = CreateSock(AF_UNIX);
if (!sock) {
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Cannot create a socket for connecting to %s\n", m_unix_socket_path);
return {};
}
const std::string path{m_unix_socket_path.substr(ADDR_PREFIX_UNIX.length())};
struct sockaddr_un addrun;
memset(&addrun, 0, sizeof(addrun));
addrun.sun_family = AF_UNIX;
// leave the last char in addrun.sun_path[] to be always '\0'
memcpy(addrun.sun_path, path.c_str(), std::min(sizeof(addrun.sun_path) - 1, path.length()));
socklen_t len = sizeof(addrun);
if(!ConnectToSocket(*sock, (struct sockaddr*)&addrun, len, path, /*manual_connection=*/true)) {
LogPrintf("Cannot connect to socket for %s\n", path);
return {};
}
return sock;
#else
return {};
#endif
}
bool SetProxy(enum Network net, const Proxy &addrProxy) {
assert(net >= 0 && net < NET_MAX);
if (!addrProxy.IsValid())
@ -633,27 +715,32 @@ bool IsProxy(const CNetAddr &addr) {
return false;
}
bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed)
std::unique_ptr<Sock> ConnectThroughProxy(const Proxy& proxy,
const std::string& dest,
uint16_t port,
bool& proxy_connection_failed)
{
// first connect to proxy server
if (!ConnectSocketDirectly(proxy.proxy, sock, nTimeout, true)) {
outProxyConnectionFailed = true;
return false;
auto sock = proxy.Connect();
if (!sock) {
proxy_connection_failed = true;
return {};
}
// do socks negotiation
if (proxy.randomize_credentials) {
if (proxy.m_randomize_credentials) {
ProxyCredentials random_auth;
static std::atomic_int counter(0);
random_auth.username = random_auth.password = strprintf("%i", counter++);
if (!Socks5(strDest, port, &random_auth, sock)) {
return false;
if (!Socks5(dest, port, &random_auth, *sock)) {
return {};
}
} else {
if (!Socks5(strDest, port, nullptr, sock)) {
return false;
if (!Socks5(dest, port, nullptr, *sock)) {
return {};
}
}
return true;
return sock;
}
CSubNet LookupSubNet(const std::string& subnet_str)

@ -27,6 +27,9 @@ static const int DEFAULT_CONNECT_TIMEOUT = 5000;
//! -dns default
static const int DEFAULT_NAME_LOOKUP = true;
/** Prefix for unix domain socket addresses (which are local filesystem paths) */
const std::string ADDR_PREFIX_UNIX = "unix:";
enum class ConnectionDirection {
None = 0,
In = (1U << 0),
@ -43,16 +46,46 @@ static inline bool operator&(ConnectionDirection a, ConnectionDirection b) {
return (underlying(a) & underlying(b));
}
/**
* Check if a string is a valid UNIX domain socket path
*
* @param name The string provided by the user representing a local path
*
* @returns Whether the string has proper format, length, and points to an existing file path
*/
bool IsUnixSocketPath(const std::string& name);
class Proxy
{
public:
Proxy(): randomize_credentials(false) {}
explicit Proxy(const CService &_proxy, bool _randomize_credentials=false): proxy(_proxy), randomize_credentials(_randomize_credentials) {}
bool IsValid() const { return proxy.IsValid(); }
Proxy() : m_is_unix_socket(false), m_randomize_credentials(false) {}
explicit Proxy(const CService& _proxy, bool _randomize_credentials = false) : proxy(_proxy), m_is_unix_socket(false), m_randomize_credentials(_randomize_credentials) {}
explicit Proxy(const std::string path, bool _randomize_credentials = false) : m_unix_socket_path(path), m_is_unix_socket(true), m_randomize_credentials(_randomize_credentials) {}
CService proxy;
bool randomize_credentials;
std::string m_unix_socket_path;
bool m_is_unix_socket;
bool m_randomize_credentials;
bool IsValid() const
{
if (m_is_unix_socket) return IsUnixSocketPath(m_unix_socket_path);
return proxy.IsValid();
}
sa_family_t GetFamily() const
{
if (m_is_unix_socket) return AF_UNIX;
return proxy.GetSAFamily();
}
std::string ToString() const
{
if (m_is_unix_socket) return m_unix_socket_path;
return proxy.ToStringAddrPort();
}
std::unique_ptr<Sock> Connect() const;
};
/** Credentials for proxy authentication */
@ -229,47 +262,42 @@ CService LookupNumeric(const std::string& name, uint16_t portDefault = 0, DNSLoo
CSubNet LookupSubNet(const std::string& subnet_str);
/**
* Create a TCP socket in the given address family.
* @param[in] address_family The socket is created in the same address family as this address.
* Create a TCP or UNIX socket in the given address family.
* @param[in] address_family to use for the socket.
* @return pointer to the created Sock object or unique_ptr that owns nothing in case of failure
*/
std::unique_ptr<Sock> CreateSockTCP(const CService& address_family);
std::unique_ptr<Sock> CreateSockOS(sa_family_t address_family);
/**
* Socket factory. Defaults to `CreateSockTCP()`, but can be overridden by unit tests.
* Socket factory. Defaults to `CreateSockOS()`, but can be overridden by unit tests.
*/
extern std::function<std::unique_ptr<Sock>(const CService&)> CreateSock;
extern std::function<std::unique_ptr<Sock>(const sa_family_t&)> CreateSock;
/**
* Try to connect to the specified service on the specified socket.
* Create a socket and try to connect to the specified service.
*
* @param addrConnect The service to which to connect.
* @param sock The socket on which to connect.
* @param nTimeout Wait this many milliseconds for the connection to be
* established.
* @param manual_connection Whether or not the connection was manually requested
* (e.g. through the addnode RPC)
* @param[in] dest The service to which to connect.
* @param[in] manual_connection Whether or not the connection was manually requested (e.g. through the addnode RPC)
*
* @returns Whether or not a connection was successfully made.
* @returns the connected socket if the operation succeeded, empty unique_ptr otherwise
*/
bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nTimeout, bool manual_connection);
std::unique_ptr<Sock> ConnectDirectly(const CService& dest, bool manual_connection);
/**
* Connect to a specified destination service through a SOCKS5 proxy by first
* connecting to the SOCKS5 proxy.
*
* @param proxy The SOCKS5 proxy.
* @param strDest The destination service to which to connect.
* @param port The destination port.
* @param sock The socket on which to connect to the SOCKS5 proxy.
* @param nTimeout Wait this many milliseconds for the connection to the SOCKS5
* proxy to be established.
* @param[out] outProxyConnectionFailed Whether or not the connection to the
* SOCKS5 proxy failed.
* @param[in] proxy The SOCKS5 proxy.
* @param[in] dest The destination service to which to connect.
* @param[in] port The destination port.
* @param[out] proxy_connection_failed Whether or not the connection to the SOCKS5 proxy failed.
*
* @returns Whether or not the operation succeeded.
* @returns the connected socket if the operation succeeded. Otherwise an empty unique_ptr.
*/
bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed);
std::unique_ptr<Sock> ConnectThroughProxy(const Proxy& proxy,
const std::string& dest,
uint16_t port,
bool& proxy_connection_failed);
/**
* Interrupt SOCKS5 reads or writes.

@ -454,20 +454,24 @@ void OptionsDialog::updateProxyValidationState()
void OptionsDialog::updateDefaultProxyNets()
{
const std::optional<CNetAddr> ui_proxy_netaddr{LookupHost(ui->proxyIp->text().toStdString(), /*fAllowLookup=*/false)};
const CService ui_proxy{ui_proxy_netaddr.value_or(CNetAddr{}), ui->proxyPort->text().toUShort()};
std::string proxyIpText{ui->proxyIp->text().toStdString()};
if (!IsUnixSocketPath(proxyIpText)) {
const std::optional<CNetAddr> ui_proxy_netaddr{LookupHost(proxyIpText, /*fAllowLookup=*/false)};
const CService ui_proxy{ui_proxy_netaddr.value_or(CNetAddr{}), ui->proxyPort->text().toUShort()};
proxyIpText = ui_proxy.ToStringAddrPort();
}
Proxy proxy;
bool has_proxy;
has_proxy = model->node().getProxy(NET_IPV4, proxy);
ui->proxyReachIPv4->setChecked(has_proxy && proxy.proxy == ui_proxy);
ui->proxyReachIPv4->setChecked(has_proxy && proxy.ToString() == proxyIpText);
has_proxy = model->node().getProxy(NET_IPV6, proxy);
ui->proxyReachIPv6->setChecked(has_proxy && proxy.proxy == ui_proxy);
ui->proxyReachIPv6->setChecked(has_proxy && proxy.ToString() == proxyIpText);
has_proxy = model->node().getProxy(NET_ONION, proxy);
ui->proxyReachTor->setChecked(has_proxy && proxy.proxy == ui_proxy);
ui->proxyReachTor->setChecked(has_proxy && proxy.ToString() == proxyIpText);
}
ProxyAddressValidator::ProxyAddressValidator(QObject *parent) :

@ -607,8 +607,8 @@ static UniValue GetNetworksInfo()
obj.pushKV("name", GetNetworkName(network));
obj.pushKV("limited", !g_reachable_nets.Contains(network));
obj.pushKV("reachable", g_reachable_nets.Contains(network));
obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringAddrPort() : std::string());
obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials);
obj.pushKV("proxy", proxy.IsValid() ? proxy.ToString() : std::string());
obj.pushKV("proxy_randomize_credentials", proxy.m_randomize_credentials);
networks.push_back(obj);
}
return networks;

@ -83,7 +83,7 @@ static const TypeTestOneInput* g_test_one_input{nullptr};
void initialize()
{
// Terminate immediately if a fuzzing harness ever tries to create a TCP socket.
CreateSock = [](const CService&) -> std::unique_ptr<Sock> { std::terminate(); };
CreateSock = [](const sa_family_t&) -> std::unique_ptr<Sock> { std::terminate(); };
// Terminate immediately if a fuzzing harness ever tries to perform a DNS lookup.
g_dns_lookup = [](const std::string& name, bool allow_lookup) {

@ -6,6 +6,7 @@
#include <i2p.h>
#include <logging.h>
#include <netaddress.h>
#include <netbase.h>
#include <test/util/logging.h>
#include <test/util/net.h>
#include <test/util/setup_common.h>
@ -38,7 +39,7 @@ public:
private:
const BCLog::Level m_prev_log_level;
const std::function<std::unique_ptr<Sock>(const CService&)> m_create_sock_orig;
const std::function<std::unique_ptr<Sock>(const sa_family_t&)> m_create_sock_orig;
};
BOOST_FIXTURE_TEST_SUITE(i2p_tests, EnvTestingSetup)
@ -46,12 +47,14 @@ BOOST_FIXTURE_TEST_SUITE(i2p_tests, EnvTestingSetup)
BOOST_AUTO_TEST_CASE(unlimited_recv)
{
// Mock CreateSock() to create MockSock.
CreateSock = [](const CService&) {
CreateSock = [](const sa_family_t&) {
return std::make_unique<StaticContentsSock>(std::string(i2p::sam::MAX_MSG_SIZE + 1, 'a'));
};
CThreadInterrupt interrupt;
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", CService{}, &interrupt);
const std::optional<CService> addr{Lookup("127.0.0.1", 9000, false)};
const Proxy sam_proxy(addr.value(), false);
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", sam_proxy, &interrupt);
{
ASSERT_DEBUG_LOG("Creating persistent SAM session");
@ -66,7 +69,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv)
BOOST_AUTO_TEST_CASE(listen_ok_accept_fail)
{
size_t num_sockets{0};
CreateSock = [&num_sockets](const CService&) {
CreateSock = [&num_sockets](const sa_family_t&) {
// clang-format off
++num_sockets;
// First socket is the control socket for creating the session.
@ -111,8 +114,10 @@ BOOST_AUTO_TEST_CASE(listen_ok_accept_fail)
};
CThreadInterrupt interrupt;
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656};
const Proxy sam_proxy(addr, false);
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key",
CService{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656},
sam_proxy,
&interrupt);
i2p::Connection conn;
@ -130,7 +135,7 @@ BOOST_AUTO_TEST_CASE(damaged_private_key)
{
const auto CreateSockOrig = CreateSock;
CreateSock = [](const CService&) {
CreateSock = [](const sa_family_t&) {
return std::make_unique<StaticContentsSock>("HELLO REPLY RESULT=OK VERSION=3.1\n"
"SESSION STATUS RESULT=OK DESTINATION=\n");
};
@ -154,7 +159,9 @@ BOOST_AUTO_TEST_CASE(damaged_private_key)
BOOST_REQUIRE(WriteBinaryFile(i2p_private_key_file, file_contents));
CThreadInterrupt interrupt;
i2p::sam::Session session(i2p_private_key_file, CService{}, &interrupt);
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656};
const Proxy sam_proxy{addr, false};
i2p::sam::Session session(i2p_private_key_file, sam_proxy, &interrupt);
{
ASSERT_DEBUG_LOG("Creating persistent SAM session");

@ -17,6 +17,7 @@ Test plan:
- support no authentication (other proxy)
- support no authentication + user/pass authentication (Tor)
- proxy on IPv6
- proxy over unix domain sockets
- Create various proxies (as threads)
- Create nodes that connect to them
@ -39,7 +40,9 @@ addnode connect to a CJDNS address
- Test passing unknown -onlynet
"""
import os
import socket
import tempfile
from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType
from test_framework.test_framework import BitcoinTestFramework
@ -47,7 +50,7 @@ from test_framework.util import (
assert_equal,
p2p_port,
)
from test_framework.netutil import test_ipv6_local
from test_framework.netutil import test_ipv6_local, test_unix_socket
# Networks returned by RPC getpeerinfo.
NET_UNROUTABLE = "not_publicly_routable"
@ -60,14 +63,17 @@ NET_CJDNS = "cjdns"
# Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo()
NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION, NET_I2P, NET_CJDNS})
# Use the shortest temp path possible since UNIX sockets may have as little as 92-char limit
socket_path = tempfile.NamedTemporaryFile().name
class ProxyTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 5
self.num_nodes = 7
self.setup_clean_chain = True
def setup_nodes(self):
self.have_ipv6 = test_ipv6_local()
self.have_unix_sockets = test_unix_socket()
# Create two proxies on different ports
# ... one unauthenticated
self.conf1 = Socks5Configuration()
@ -89,6 +95,15 @@ class ProxyTest(BitcoinTestFramework):
else:
self.log.warning("Testing without local IPv6 support")
if self.have_unix_sockets:
self.conf4 = Socks5Configuration()
self.conf4.af = socket.AF_UNIX
self.conf4.addr = socket_path
self.conf4.unauth = True
self.conf4.auth = True
else:
self.log.warning("Testing without local unix domain sockets support")
self.serv1 = Socks5Server(self.conf1)
self.serv1.start()
self.serv2 = Socks5Server(self.conf2)
@ -96,6 +111,9 @@ class ProxyTest(BitcoinTestFramework):
if self.have_ipv6:
self.serv3 = Socks5Server(self.conf3)
self.serv3.start()
if self.have_unix_sockets:
self.serv4 = Socks5Server(self.conf4)
self.serv4.start()
# We will not try to connect to this.
self.i2p_sam = ('127.0.0.1', 7656)
@ -109,10 +127,15 @@ class ProxyTest(BitcoinTestFramework):
['-listen', f'-proxy={self.conf2.addr[0]}:{self.conf2.addr[1]}','-proxyrandomize=1'],
[],
['-listen', f'-proxy={self.conf1.addr[0]}:{self.conf1.addr[1]}','-proxyrandomize=1',
'-cjdnsreachable']
'-cjdnsreachable'],
[],
[]
]
if self.have_ipv6:
args[3] = ['-listen', f'-proxy=[{self.conf3.addr[0]}]:{self.conf3.addr[1]}','-proxyrandomize=0', '-noonion']
if self.have_unix_sockets:
args[5] = ['-listen', f'-proxy=unix:{socket_path}']
args[6] = ['-listen', f'-onion=unix:{socket_path}']
self.add_nodes(self.num_nodes, extra_args=args)
self.start_nodes()
@ -124,7 +147,7 @@ class ProxyTest(BitcoinTestFramework):
def node_test(self, node, *, proxies, auth, test_onion, test_cjdns):
rv = []
addr = "15.61.23.23:1234"
self.log.debug(f"Test: outgoing IPv4 connection through node for address {addr}")
self.log.debug(f"Test: outgoing IPv4 connection through node {node.index} for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[0].queue.get()
assert isinstance(cmd, Socks5Command)
@ -140,7 +163,7 @@ class ProxyTest(BitcoinTestFramework):
if self.have_ipv6:
addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443"
self.log.debug(f"Test: outgoing IPv6 connection through node for address {addr}")
self.log.debug(f"Test: outgoing IPv6 connection through node {node.index} for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[1].queue.get()
assert isinstance(cmd, Socks5Command)
@ -156,7 +179,7 @@ class ProxyTest(BitcoinTestFramework):
if test_onion:
addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333"
self.log.debug(f"Test: outgoing onion connection through node for address {addr}")
self.log.debug(f"Test: outgoing onion connection through node {node.index} for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[2].queue.get()
assert isinstance(cmd, Socks5Command)
@ -171,7 +194,7 @@ class ProxyTest(BitcoinTestFramework):
if test_cjdns:
addr = "[fc00:1:2:3:4:5:6:7]:8888"
self.log.debug(f"Test: outgoing CJDNS connection through node for address {addr}")
self.log.debug(f"Test: outgoing CJDNS connection through node {node.index} for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[1].queue.get()
assert isinstance(cmd, Socks5Command)
@ -185,7 +208,7 @@ class ProxyTest(BitcoinTestFramework):
self.network_test(node, addr, network=NET_CJDNS)
addr = "node.noumenon:8333"
self.log.debug(f"Test: outgoing DNS name connection through node for address {addr}")
self.log.debug(f"Test: outgoing DNS name connection through node {node.index} for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[3].queue.get()
assert isinstance(cmd, Socks5Command)
@ -230,6 +253,12 @@ class ProxyTest(BitcoinTestFramework):
proxies=[self.serv1, self.serv1, self.serv1, self.serv1],
auth=False, test_onion=True, test_cjdns=True)
if self.have_unix_sockets:
self.node_test(self.nodes[5],
proxies=[self.serv4, self.serv4, self.serv4, self.serv4],
auth=True, test_onion=True, test_cjdns=False)
def networks_dict(d):
r = {}
for x in d['networks']:
@ -315,6 +344,37 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n4['i2p']['reachable'], False)
assert_equal(n4['cjdns']['reachable'], True)
if self.have_unix_sockets:
n5 = networks_dict(nodes_network_info[5])
assert_equal(NETWORKS, n5.keys())
for net in NETWORKS:
if net == NET_I2P:
expected_proxy = ''
expected_randomize = False
else:
expected_proxy = 'unix:' + self.conf4.addr # no port number
expected_randomize = True
assert_equal(n5[net]['proxy'], expected_proxy)
assert_equal(n5[net]['proxy_randomize_credentials'], expected_randomize)
assert_equal(n5['onion']['reachable'], True)
assert_equal(n5['i2p']['reachable'], False)
assert_equal(n5['cjdns']['reachable'], False)
n6 = networks_dict(nodes_network_info[6])
assert_equal(NETWORKS, n6.keys())
for net in NETWORKS:
if net != NET_ONION:
expected_proxy = ''
expected_randomize = False
else:
expected_proxy = 'unix:' + self.conf4.addr # no port number
expected_randomize = True
assert_equal(n6[net]['proxy'], expected_proxy)
assert_equal(n6[net]['proxy_randomize_credentials'], expected_randomize)
assert_equal(n6['onion']['reachable'], True)
assert_equal(n6['i2p']['reachable'], False)
assert_equal(n6['cjdns']['reachable'], False)
self.stop_node(1)
self.log.info("Test passing invalid -proxy hostname raises expected init error")
@ -383,6 +443,18 @@ class ProxyTest(BitcoinTestFramework):
msg = "Error: Unknown network specified in -onlynet: 'abc'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
self.log.info("Test passing too-long unix path to -proxy raises init error")
self.nodes[1].extra_args = [f"-proxy=unix:{'x' * 1000}"]
if self.have_unix_sockets:
msg = f"Error: Invalid -proxy address or hostname: 'unix:{'x' * 1000}'"
else:
# If unix sockets are not supported, the file path is incorrectly interpreted as host:port
msg = f"Error: Invalid port specified in -proxy: 'unix:{'x' * 1000}'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
# Cleanup socket path we established outside the individual test directory.
if self.have_unix_sockets:
os.unlink(socket_path)
if __name__ == '__main__':
ProxyTest().main()

@ -158,3 +158,12 @@ def test_ipv6_local():
except socket.error:
have_ipv6 = False
return have_ipv6
def test_unix_socket():
'''Return True if UNIX sockets are available on this platform.'''
try:
socket.AF_UNIX
except AttributeError:
return False
else:
return True

Loading…
Cancel
Save