aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac15
-rw-r--r--doc/release-notes-27375.md6
-rw-r--r--src/compat/compat.h7
-rw-r--r--src/i2p.cpp14
-rw-r--r--src/i2p.h9
-rw-r--r--src/init.cpp42
-rw-r--r--src/net.cpp32
-rw-r--r--src/netaddress.cpp13
-rw-r--r--src/netaddress.h5
-rw-r--r--src/netbase.cpp175
-rw-r--r--src/netbase.h86
-rw-r--r--src/qt/optionsdialog.cpp14
-rw-r--r--src/rpc/net.cpp4
-rw-r--r--src/test/fuzz/fuzz.cpp2
-rw-r--r--src/test/i2p_tests.cpp21
-rwxr-xr-xtest/functional/feature_proxy.py88
-rw-r--r--test/functional/test_framework/netutil.py9
17 files changed, 401 insertions, 141 deletions
diff --git a/configure.ac b/configure.ac
index aeb8b71437..03b5ff1d69 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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(
diff --git a/doc/release-notes-27375.md b/doc/release-notes-27375.md
new file mode 100644
index 0000000000..e3f4ebdf77
--- /dev/null
+++ b/doc/release-notes-27375.md
@@ -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) \ No newline at end of file
diff --git a/src/compat/compat.h b/src/compat/compat.h
index 9ff9a335f8..366c648ae7 100644
--- a/src/compat/compat.h
+++ b/src/compat/compat.h
@@ -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).
diff --git a/src/i2p.cpp b/src/i2p.cpp
index 02f2c1cea2..962adb124d 100644
--- a/src/i2p.cpp
+++ b/src/i2p.cpp
@@ -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();
diff --git a/src/i2p.h b/src/i2p.h
index 375abaccfc..8b0f1e1182 100644
--- a/src/i2p.h
+++ b/src/i2p.h
@@ -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.
diff --git a/src/init.cpp b/src/init.cpp
index 0ecfba18c1..1a4fce4678 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -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};
}
}
diff --git a/src/net.cpp b/src/net.cpp
index ab462b2994..e388f05b03 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -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) {
diff --git a/src/netaddress.cpp b/src/netaddress.cpp
index 7530334db1..74ab6dd8d8 100644
--- a/src/netaddress.cpp
+++ b/src/netaddress.cpp
@@ -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;
diff --git a/src/netaddress.h b/src/netaddress.h
index c697b7e0a3..c63bd4b4e5 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -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);
diff --git a/src/netbase.cpp b/src/netbase.cpp
index 49c334d332..61e96c0657 100644
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -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)
diff --git a/src/netbase.h b/src/netbase.h
index 1bd95ba0d9..321c288f67 100644
--- a/src/netbase.h
+++ b/src/netbase.h
@@ -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.
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index a87bef796c..dd654a7abe 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -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) :
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 5e6f42b596..3fa2b18495 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -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;
diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp
index d1a67cb0d8..a8e490b459 100644
--- a/src/test/fuzz/fuzz.cpp
+++ b/src/test/fuzz/fuzz.cpp
@@ -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) {
diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp
index f80f07d190..d7249d88f4 100644
--- a/src/test/i2p_tests.cpp
+++ b/src/test/i2p_tests.cpp
@@ -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");
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index 662007d65e..7a6f639021 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -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()
diff --git a/test/functional/test_framework/netutil.py b/test/functional/test_framework/netutil.py
index 30a4a58d6f..08d41fe97f 100644
--- a/test/functional/test_framework/netutil.py
+++ b/test/functional/test_framework/netutil.py
@@ -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