Merge 0ed2c130e7 into merged_master (Bitcoin PR bitcoin/bitcoin#27375)

This commit is contained in:
ivanlele 2026-02-16 09:59:44 +00:00
commit e893b7e6f9
No known key found for this signature in database
17 changed files with 401 additions and 142 deletions

View file

@ -1211,10 +1211,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(

View file

@ -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)

View file

@ -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).

View file

@ -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();

View file

@ -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.

View file

@ -547,7 +547,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);
@ -558,7 +562,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);
@ -1469,7 +1477,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
}
}
}
@ -1536,12 +1551,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));
@ -1567,11 +1588,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};
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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);

View file

@ -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;
// Create a TCP socket in the address family of the specified service.
SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
int protocol{IPPROTO_TCP};
#if HAVE_SOCKADDR_UN
if (address_family == AF_UNIX) protocol = 0;
#endif
// 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)

View file

@ -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.

View file

@ -456,20 +456,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) :

View file

@ -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;

View file

@ -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) {

View file

@ -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");

View file

@ -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,8 +40,9 @@ addnode connect to a CJDNS address
- Test passing unknown -onlynet
"""
import socket
import os
import socket
import tempfile
from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType
from test_framework.test_framework import BitcoinTestFramework
@ -49,7 +51,7 @@ from test_framework.util import (
PORT_RANGE,
assert_equal,
)
from test_framework.netutil import test_ipv6_local
from test_framework.netutil import test_ipv6_local, test_unix_socket
RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports
@ -64,14 +66,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()
@ -93,6 +98,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)
@ -100,6 +114,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)
@ -113,10 +130,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()
@ -128,7 +150,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)
@ -144,7 +166,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)
@ -160,7 +182,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)
@ -175,7 +197,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)
@ -189,7 +211,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)
@ -234,6 +256,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']:
@ -319,6 +347,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")
@ -387,6 +446,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()

View file

@ -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