-
Notifications
You must be signed in to change notification settings - Fork 35.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
p2p: Bare minimum to support UNIX sockets #9979
Conversation
Add functionality for RPC over UNIX sockets, on platforms that support UNIX sockets. This is specified with `:unix:/path/to/socket` to `-rpcbind` and `-rpcconnect`. By default the socket path is relative to the data directory.
evhttp has no direct support for UNIX sockets thus is not able to recognize UNIX peers. Add custom code in HTTPRequest::GetPeer and evunix to recognize these connections as coming from localhost. The previous solution did not work on some libevent versions.
Make it possible to listen and connect to UNIX sockets for P2P. There are two main use cases for this: - Testing without possibility of port collisions - TOR hidden service listening with less risk of privacy leaks by not opening any internet ports Usage: specify `:unix:<path>` for `bind`, `whitebind`, `connect`, `addnode`.
cd59f8e
to
9abf3c9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I'm familiar enough with libevent to give more than a concept ack. I read through it & it looks great. Concept Ack.
@@ -331,6 +337,8 @@ static bool HTTPBindAddresses(struct evhttp* http) | |||
} else if (mapMultiArgs.count("-rpcbind")) { // Specific bind address | |||
const std::vector<std::string>& vbind = mapMultiArgs.at("-rpcbind"); | |||
for (std::vector<std::string>::const_iterator i = vbind.begin(); i != vbind.end(); ++i) { | |||
if (boost::starts_with(*i, RPC_ADDR_PREFIX_UNIX)) // Skip UNIX sockets here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an easy way to replicate this without a new boost dep is:
if (!i->rfind(RPC_ADD_PREFIX_UNIX, 0))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like boost::starts_with. It's python idom (and many other languages) so easy to read. If we want to do without that, I think we should simply implement our own in terms of what you write.
raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port); | ||
raii_evhttp_connection evcon; | ||
if (boost::starts_with(host, RPC_ADDR_PREFIX_UNIX)) { | ||
#if defined(HAVE_SOCKADDR_UN) && defined(LIBEVENT_EXPERIMENTAL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be better to have the ifdef define two different functions HandleUnixSocket.
I'm always nervous with compiler directives inside functions, unless the code switched is ~1 line. Large ifdefs are in a couple places in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, factoring this out to a function makes sense
@@ -505,6 +547,12 @@ void StopHTTPServer() | |||
event_base_free(eventBase); | |||
eventBase = 0; | |||
} | |||
#ifdef HAVE_SOCKADDR_UN | |||
//! Clean up UNIX sockets on shutdown | |||
for (const auto& path: cleanupUNIXSockets) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slight preference for something with ownership guarantees of
std::vector<std::unique_ptr<void>> cleanupUNIXSockets;
// must be non null value
cleanupUNIXSockets.emplace_back(1, [=](void*) { evunix_remove_socket(path); });
cleanupUNIXSockets.clear();
so that sockets aren't double removed.
Maybe wrap the evunix stuff in a class and just keep a vector of those around?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, makes sense
src/httpserver.cpp
Outdated
@@ -619,6 +667,12 @@ CService HTTPRequest::GetPeer() | |||
const char* address = ""; | |||
uint16_t port = 0; | |||
evhttp_connection_get_peer(con, (char**)&address, &port); | |||
if (!strcmp(address, "localhost")) { | |||
/* Special: will get here for UNIX sockets. As we have no way to indicate that, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
recommend comment language (took me a minute to understand your comment because of ambiguity on here and that):
We will get an address of localhost either via a unix socket or via a localhost peer. There is no sane way to return an address for a socket, so we always return localhost's IPv6.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 9abf3c9, though many files are out of date.
I'm wondering why this is labeled "bare minimum to support UNIX sockets" and when support seems pretty complete. Are there other p2p features over unix sockets that you'd want to support?
// Listen for incoming connections | ||
if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) | ||
{ | ||
strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "p2p: Bare minimum to support UNIX sockets"
Any reason this error is translated while others are not?
return false; | ||
} | ||
SOCKET hListenSocket = (SOCKET)fd; | ||
LogPrintf("P2P bound to %s\n", path.string()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "p2p: Bare minimum to support UNIX sockets"
Maybe mention path is unix socket path
Thanks for reviewing @JeremyRubin @jnewbery
I somewhat dread having to rebase this, but hope to do so in the near future.
There's no support in RPC calls yet, for example. Unix sockets can only be passed on the command line. |
Yes - rebase looks like it's going to be a big job :( Let me know if I can help in any way - I'm definitely interested in getting Unix socket support for RPC. |
I didn't try rebasing, but the merge with master doesn't look bad at all. The only C++ code conflicts are mapMultiArgs -> gArgs replacements. There is a huge scary looking conflict in the test framework util.py, but it just comes from the initialize_chain function moving to another file, and the changes there are minor. |
Closing this for now. I'll probably pick it up again for 0.16. |
Two commit on top of #9919 (first adds the implementation, second adds a test).
Make it possible to listen and connect to UNIX sockets for P2P. There are two main use cases for this:
Usage: specify
:unix:<path>
forbind
,whitebind
,connect
,addnode
.