Skip to content

Commit 0ed2c13

Browse files
committed
Merge bitcoin#27375: net: support unix domain sockets for -proxy and -onion
567cec9 doc: add release notes and help text for unix sockets (Matthew Zipkin) bfe5192 test: cover UNIX sockets in feature_proxy.py (Matthew Zipkin) c65c0d0 init: allow UNIX socket path for -proxy and -onion (Matthew Zipkin) c3bd431 gui: accomodate unix socket Proxy in updateDefaultProxyNets() (Matthew Zipkin) a88bf9d i2p: construct Session with Proxy instead of CService (Matthew Zipkin) d9318a3 net: split ConnectToSocket() from ConnectDirectly() for unix sockets (Matthew Zipkin) ac2ecf3 proxy: rename randomize_credentials to m_randomize_credentials (Matthew Zipkin) a89c3f5 netbase: extend Proxy class to wrap UNIX socket as well as TCP (Matthew Zipkin) 3a7d654 net: move CreateSock() calls from ConnectNode() to netbase methods (Matthew Zipkin) 74f568c netbase: allow CreateSock() to create UNIX sockets if supported (Matthew Zipkin) bae86c8 netbase: refactor CreateSock() to accept sa_family_t (Matthew Zipkin) adb3a3e configure: test for unix domain sockets (Matthew Zipkin) Pull request description: Closes bitcoin#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](bitcoin#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 (bitcoin#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 567cec9 tdb3: re ACK for 567cec9. achow101: ACK 567cec9 vasild: ACK 567cec9 Tree-SHA512: de81860e56d5de83217a18df4c35297732b4ad491e293a0153d2d02a0bde1d022700a1131279b187ef219651487537354b9d06d10fde56225500c7e257df92c1
2 parents 1105aa4 + 567cec9 commit 0ed2c13

File tree

17 files changed

+401
-141
lines changed

17 files changed

+401
-141
lines changed

configure.ac

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1198,10 +1198,23 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
11981198
]], [[
11991199
getauxval(AT_HWCAP);
12001200
]])],
1201-
[ AC_MSG_RESULT([yes]); HAVE_STRONG_GETAUXVAL=1; AC_DEFINE([HAVE_STRONG_GETAUXVAL], [1], [Define this symbol to build code that uses getauxval)]) ],
1201+
[ AC_MSG_RESULT([yes]); HAVE_STRONG_GETAUXVAL=1; AC_DEFINE([HAVE_STRONG_GETAUXVAL], [1], [Define this symbol to build code that uses getauxval]) ],
12021202
[ AC_MSG_RESULT([no]); HAVE_STRONG_GETAUXVAL=0 ]
12031203
)
12041204

1205+
# Check for UNIX sockets
1206+
AC_MSG_CHECKING(for sockaddr_un)
1207+
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
1208+
#include <sys/socket.h>
1209+
#include <sys/un.h>
1210+
]], [[
1211+
struct sockaddr_un addr;
1212+
addr.sun_family = AF_UNIX;
1213+
]])],
1214+
[ AC_MSG_RESULT([yes]); AC_DEFINE([HAVE_SOCKADDR_UN], [1], [Define this symbol if platform supports unix domain sockets]) ],
1215+
[ AC_MSG_RESULT([no]); ]
1216+
)
1217+
12051218
have_any_system=no
12061219
AC_MSG_CHECKING([for std::system])
12071220
AC_LINK_IFELSE(

doc/release-notes-27375.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
P2P
2+
---
3+
4+
UNIX domain sockets can now be used for proxy connections. Set `-onion` or `-proxy`
5+
to the local socket path with the prefix `unix:` (e.g. `-onion=unix:/home/me/torsocket`).
6+
(#27375)

src/compat/compat.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
#include <unistd.h> // IWYU pragma: export
3333
#endif
3434

35+
// Windows does not have `sa_family_t` - it defines `sockaddr::sa_family` as `u_short`.
36+
// Thus define `sa_family_t` on Windows too so that the rest of the code can use `sa_family_t`.
37+
// See https://learn.microsoft.com/en-us/windows/win32/api/winsock/ns-winsock-sockaddr#syntax
38+
#ifdef WIN32
39+
typedef u_short sa_family_t;
40+
#endif
41+
3542
// We map Linux / BSD error functions and codes, to the equivalent
3643
// Windows definitions, and use the WSA* names throughout our code.
3744
// Note that glibc defines EWOULDBLOCK as EAGAIN (see errno.h).

src/i2p.cpp

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ static CNetAddr DestB64ToAddr(const std::string& dest)
115115
namespace sam {
116116

117117
Session::Session(const fs::path& private_key_file,
118-
const CService& control_host,
118+
const Proxy& control_host,
119119
CThreadInterrupt* interrupt)
120120
: m_private_key_file{private_key_file},
121121
m_control_host{control_host},
@@ -124,7 +124,7 @@ Session::Session(const fs::path& private_key_file,
124124
{
125125
}
126126

127-
Session::Session(const CService& control_host, CThreadInterrupt* interrupt)
127+
Session::Session(const Proxy& control_host, CThreadInterrupt* interrupt)
128128
: m_control_host{control_host},
129129
m_interrupt{interrupt},
130130
m_transient{true}
@@ -327,14 +327,10 @@ Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
327327

328328
std::unique_ptr<Sock> Session::Hello() const
329329
{
330-
auto sock = CreateSock(m_control_host);
330+
auto sock = m_control_host.Connect();
331331

332332
if (!sock) {
333-
throw std::runtime_error("Cannot create socket");
334-
}
335-
336-
if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout, true)) {
337-
throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToStringAddrPort()));
333+
throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));
338334
}
339335

340336
SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1");
@@ -418,7 +414,7 @@ void Session::CreateIfNotCreatedAlready()
418414
const auto session_type = m_transient ? "transient" : "persistent";
419415
const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs
420416

421-
Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToStringAddrPort());
417+
Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToString());
422418

423419
auto sock = Hello();
424420

src/i2p.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <compat/compat.h>
99
#include <netaddress.h>
10+
#include <netbase.h>
1011
#include <sync.h>
1112
#include <util/fs.h>
1213
#include <util/sock.h>
@@ -67,7 +68,7 @@ class Session
6768
* `Session` object.
6869
*/
6970
Session(const fs::path& private_key_file,
70-
const CService& control_host,
71+
const Proxy& control_host,
7172
CThreadInterrupt* interrupt);
7273

7374
/**
@@ -81,7 +82,7 @@ class Session
8182
* `CThreadInterrupt` object is saved, so it must not be destroyed earlier than this
8283
* `Session` object.
8384
*/
84-
Session(const CService& control_host, CThreadInterrupt* interrupt);
85+
Session(const Proxy& control_host, CThreadInterrupt* interrupt);
8586

8687
/**
8788
* Destroy the session, closing the internally used sockets. The sockets that have been
@@ -235,9 +236,9 @@ class Session
235236
const fs::path m_private_key_file;
236237

237238
/**
238-
* The host and port of the SAM control service.
239+
* The SAM control service proxy.
239240
*/
240-
const CService m_control_host;
241+
const Proxy m_control_host;
241242

242243
/**
243244
* Cease network activity when this is signaled.

src/init.cpp

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,11 @@ void SetupServerArgs(ArgsManager& argsman)
534534
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);
535535
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);
536536
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);
537+
#if HAVE_SOCKADDR_UN
538+
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);
539+
#else
537540
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);
541+
#endif
538542
argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
539543
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);
540544
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)
545549
// TODO: remove the sentence "Nodes not using ... incoming connections." once the changes from
546550
// https://github.com/bitcoin/bitcoin/pull/23542 have become widespread.
547551
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);
552+
#if HAVE_SOCKADDR_UN
553+
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);
554+
#else
548555
argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION);
556+
#endif
549557
argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
550558
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);
551559
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)
13231331
std::string host_out;
13241332
uint16_t port_out{0};
13251333
if (!SplitHostPort(socket_addr, port_out, host_out)) {
1334+
#if HAVE_SOCKADDR_UN
1335+
// Allow unix domain sockets for -proxy and -onion e.g. unix:/some/file/path
1336+
if ((port_option != "-proxy" && port_option != "-onion") || socket_addr.find(ADDR_PREFIX_UNIX) != 0) {
1337+
return InitError(InvalidPortErrMsg(port_option, socket_addr));
1338+
}
1339+
#else
13261340
return InitError(InvalidPortErrMsg(port_option, socket_addr));
1341+
#endif
13271342
}
13281343
}
13291344
}
@@ -1390,12 +1405,18 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
13901405
// -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default
13911406
std::string proxyArg = args.GetArg("-proxy", "");
13921407
if (proxyArg != "" && proxyArg != "0") {
1393-
const std::optional<CService> proxyAddr{Lookup(proxyArg, 9050, fNameLookup)};
1394-
if (!proxyAddr.has_value()) {
1395-
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
1408+
Proxy addrProxy;
1409+
if (IsUnixSocketPath(proxyArg)) {
1410+
addrProxy = Proxy(proxyArg, proxyRandomize);
1411+
} else {
1412+
const std::optional<CService> proxyAddr{Lookup(proxyArg, 9050, fNameLookup)};
1413+
if (!proxyAddr.has_value()) {
1414+
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
1415+
}
1416+
1417+
addrProxy = Proxy(proxyAddr.value(), proxyRandomize);
13961418
}
13971419

1398-
Proxy addrProxy = Proxy(proxyAddr.value(), proxyRandomize);
13991420
if (!addrProxy.IsValid())
14001421
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
14011422

@@ -1421,11 +1442,16 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
14211442
"reaching the Tor network is explicitly forbidden: -onion=0"));
14221443
}
14231444
} else {
1424-
const std::optional<CService> addr{Lookup(onionArg, 9050, fNameLookup)};
1425-
if (!addr.has_value() || !addr->IsValid()) {
1426-
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
1445+
if (IsUnixSocketPath(onionArg)) {
1446+
onion_proxy = Proxy(onionArg, proxyRandomize);
1447+
} else {
1448+
const std::optional<CService> addr{Lookup(onionArg, 9050, fNameLookup)};
1449+
if (!addr.has_value() || !addr->IsValid()) {
1450+
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
1451+
}
1452+
1453+
onion_proxy = Proxy(addr.value(), proxyRandomize);
14271454
}
1428-
onion_proxy = Proxy{addr.value(), proxyRandomize};
14291455
}
14301456
}
14311457

src/net.cpp

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,6 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
437437
}
438438

439439
// Connect
440-
bool connected = false;
441440
std::unique_ptr<Sock> sock;
442441
Proxy proxy;
443442
CAddress addr_bind;
@@ -450,6 +449,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
450449

451450
if (addrConnect.IsI2P() && use_proxy) {
452451
i2p::Connection conn;
452+
bool connected{false};
453453

454454
if (m_i2p_sam_session) {
455455
connected = m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed);
@@ -458,7 +458,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
458458
LOCK(m_unused_i2p_sessions_mutex);
459459
if (m_unused_i2p_sessions.empty()) {
460460
i2p_transient_session =
461-
std::make_unique<i2p::sam::Session>(proxy.proxy, &interruptNet);
461+
std::make_unique<i2p::sam::Session>(proxy, &interruptNet);
462462
} else {
463463
i2p_transient_session.swap(m_unused_i2p_sessions.front());
464464
m_unused_i2p_sessions.pop();
@@ -478,39 +478,25 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
478478
addr_bind = CAddress{conn.me, NODE_NONE};
479479
}
480480
} else if (use_proxy) {
481-
sock = CreateSock(proxy.proxy);
482-
if (!sock) {
483-
return nullptr;
484-
}
485-
connected = ConnectThroughProxy(proxy, addrConnect.ToStringAddr(), addrConnect.GetPort(),
486-
*sock, nConnectTimeout, proxyConnectionFailed);
481+
LogPrintLevel(BCLog::PROXY, BCLog::Level::Debug, "Using proxy: %s to connect to %s:%s\n", proxy.ToString(), addrConnect.ToStringAddr(), addrConnect.GetPort());
482+
sock = ConnectThroughProxy(proxy, addrConnect.ToStringAddr(), addrConnect.GetPort(), proxyConnectionFailed);
487483
} else {
488484
// no proxy needed (none set for target network)
489-
sock = CreateSock(addrConnect);
490-
if (!sock) {
491-
return nullptr;
492-
}
493-
connected = ConnectSocketDirectly(addrConnect, *sock, nConnectTimeout,
494-
conn_type == ConnectionType::MANUAL);
485+
sock = ConnectDirectly(addrConnect, conn_type == ConnectionType::MANUAL);
495486
}
496487
if (!proxyConnectionFailed) {
497488
// If a connection to the node was attempted, and failure (if any) is not caused by a problem connecting to
498489
// the proxy, mark this as an attempt.
499490
addrman.Attempt(addrConnect, fCountFailure);
500491
}
501492
} else if (pszDest && GetNameProxy(proxy)) {
502-
sock = CreateSock(proxy.proxy);
503-
if (!sock) {
504-
return nullptr;
505-
}
506493
std::string host;
507494
uint16_t port{default_port};
508495
SplitHostPort(std::string(pszDest), port, host);
509496
bool proxyConnectionFailed;
510-
connected = ConnectThroughProxy(proxy, host, port, *sock, nConnectTimeout,
511-
proxyConnectionFailed);
497+
sock = ConnectThroughProxy(proxy, host, port, proxyConnectionFailed);
512498
}
513-
if (!connected) {
499+
if (!sock) {
514500
return nullptr;
515501
}
516502

@@ -2990,7 +2976,7 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError,
29902976
return false;
29912977
}
29922978

2993-
std::unique_ptr<Sock> sock = CreateSock(addrBind);
2979+
std::unique_ptr<Sock> sock = CreateSock(addrBind.GetSAFamily());
29942980
if (!sock) {
29952981
strError = strprintf(Untranslated("Couldn't open socket for incoming connections (socket returned error %s)"), NetworkErrorString(WSAGetLastError()));
29962982
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original);
@@ -3197,7 +3183,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
31973183
Proxy i2p_sam;
31983184
if (GetProxy(NET_I2P, i2p_sam) && connOptions.m_i2p_accept_incoming) {
31993185
m_i2p_sam_session = std::make_unique<i2p::sam::Session>(gArgs.GetDataDirNet() / "i2p_private_key",
3200-
i2p_sam.proxy, &interruptNet);
3186+
i2p_sam, &interruptNet);
32013187
}
32023188

32033189
for (const auto& strDest : connOptions.vSeedNodes) {

src/netaddress.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,19 @@ bool CService::SetSockAddr(const struct sockaddr *paddr)
818818
}
819819
}
820820

821+
sa_family_t CService::GetSAFamily() const
822+
{
823+
switch (m_net) {
824+
case NET_IPV4:
825+
return AF_INET;
826+
case NET_IPV6:
827+
case NET_CJDNS:
828+
return AF_INET6;
829+
default:
830+
return AF_UNSPEC;
831+
}
832+
}
833+
821834
uint16_t CService::GetPort() const
822835
{
823836
return port;

src/netaddress.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,11 @@ class CService : public CNetAddr
540540
uint16_t GetPort() const;
541541
bool GetSockAddr(struct sockaddr* paddr, socklen_t* addrlen) const;
542542
bool SetSockAddr(const struct sockaddr* paddr);
543+
/**
544+
* Get the address family
545+
* @returns AF_UNSPEC if unspecified
546+
*/
547+
[[nodiscard]] sa_family_t GetSAFamily() const;
543548
friend bool operator==(const CService& a, const CService& b);
544549
friend bool operator!=(const CService& a, const CService& b) { return !(a == b); }
545550
friend bool operator<(const CService& a, const CService& b);

0 commit comments

Comments
 (0)