Skip to content
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

UNIX sockets support for RPC #9919

Closed
wants to merge 4 commits into from
Closed

Conversation

laanwj
Copy link
Member

@laanwj laanwj commented Mar 4, 2017

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.

For the server side this works as-is.

For the client side (bitcoin-cli) this requires a small patch to libevent to be able to pass in a file descriptor of an existing connection (libevent/libevent#479).

Even without the client change this could be useful though. For example the Python RPC tests could connect through UNIX socket to avoid port collisions (TODO: make a Python example). This has been implemented.

Also the overhead of using UNIX sockets should be lower than using local TCP (see https://stackoverflow.com/questions/14973942/performance-tcp-loopback-connection-vs-unix-domain-socket).

Example

Server:

src/bitcoind -printtoconsole -datadir=/store/tmp/testbtc -connect=0 -debug=rpc -debug=http -rpcbind=:unix:socket
...
2017-03-05 10:29:31 Binding RPC on UNIX socket /store/tmp/testbtc/socket
...

Client:

src/bitcoin-cli -rpcconnect=":unix:socket" -datadir=/store/tmp/testbtc getinfo
{
  "version": 149900,
  ...
}

@@ -377,6 +381,9 @@ bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPN

# bitcoin-cli binary #
bitcoin_cli_SOURCES = bitcoin-cli.cpp
if BUILD_EVUNIX
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theuni This is pretty ugly, but I don't quite know how to do this otherwise. My first intuition was to add the file to libbitcoin_util_a_SOURCES, however these can't depend on libevent as that is shared between all executables. There is no library that is shared between just bitcoind and bitcoin-cli and adding one just for this seems overkill.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is OK. I think we'll end up with a few wrappers for libevent, so moving those to their own internal helper lib will make sense.

@laanwj laanwj force-pushed the 2017_03_unix_socket branch 2 times, most recently from d374f11 to 836d2d5 Compare March 4, 2017 16:16
@gmaxwell
Copy link
Contributor

gmaxwell commented Mar 4, 2017

Neat.

@paveljanik
Copy link
Contributor

paveljanik commented Mar 4, 2017

This does not compile with libevent2-2.0.22 which is OK with the current master.

bitcoin-cli.cpp:214:13: error: use of undeclared identifier `evhttp_connection_base_bufferevent_new'
            evhttp_connection_base_bufferevent_new(base.get(), __null, bev, "", 0)
            ^
1 error generated.

https://github.com/libevent/libevent/blob/e7ff4ef2b4fc950a765008c18e74281cdb5e7668/whatsnew-2.1.txt#L422

@laanwj
Copy link
Member Author

laanwj commented Mar 4, 2017

@paveljanik Yes, that needs a version #ifdef, that call doesn't exist in older versions of libevent, and on newer ones (until my patch lands) it ignores the current connection and tries to open a new one so that trick doesn't work. I'd recommend testing with my patched libevent. Alternatively comment out the bitcoin-cli change and test connecting with something else:

  • nc -U <socket> works, for example
  • curl also has a --unix-socket parameter, but haven't tried it yet

@TheBlueMatt
Copy link
Contributor

TheBlueMatt commented Mar 4, 2017 via email

@laanwj
Copy link
Member Author

laanwj commented Mar 4, 2017

For merge we should probably just not have the client version, then?

Would prefer putting it behind #ifdef EXPERIMENTAL_LIBEVENT or such, with a comment that it can be replaced with the appropriate version comparison when it lands in a version. That way, people that compile their own stuff can use it already.

@laanwj
Copy link
Member Author

laanwj commented Mar 5, 2017

Still, this is cool - lots of better auth controls can be applied to
sockets than IPs :)

Indeed. Default permissions of the socket will be 700 due to our umask setting (can be overridden with -sysperms), which seems okay:

srwx------  1 user user          0 Mar  5 11:40 socket

@jonasschnelli
Copy link
Contributor

This is great!
Concept ACK.

@laanwj
Copy link
Member Author

laanwj commented Mar 6, 2017

Added a commit that makes the tests' RPC run over UNIX sockets on platforms that support it, except for a few tests that make explicit assumptions about running over TCP/IP.

@laanwj laanwj force-pushed the 2017_03_unix_socket branch 3 times, most recently from 6f9286c to 971af66 Compare March 6, 2017 20:46
@jnewbery
Copy link
Contributor

jnewbery commented Mar 6, 2017

I've been trying to test this locally by running the qa tests. Something that I do quite often is to run the tests with --noshutdown, wait for the test to fail, and use bitcoin-cli to interactively run RPCs on the test nodes:

bitcoin.conf -conf=<test temp directory>/node<x>/bitcoin.conf

With this PR, I'm not able to do that anymore because the conf file doesn't contain the username/password, and even when I add them back in, I get the error:

/tmp/user/1000/test_6o70u2b/28404/node0
→ ~/bitcoin/src/bitcoin-cli -rpcconnect=":unix:rpc_socket" -datadir=/tmp/user/1000/test_6o70u2b/28404/node0 help
[warn] evhttp_connection_connect_: connection to "" failed: No such file or directory
error: couldn't connect to server: unknown (code -1)
(make sure server is running and you are connecting to the correct RPC port)
/tmp/user/1000/test_6o70u2b/28404/node0
→ ll
total 16
drwxrwxr-x 3 ubuntu ubuntu 4096 Mar  6 23:31 ./
drwxrwxr-x 4 ubuntu ubuntu 4096 Mar  6 23:11 ../
-rw-rw-r-- 1 ubuntu ubuntu   79 Mar  6 23:20 bitcoin.conf
drwx------ 5 ubuntu ubuntu 4096 Mar  6 23:26 regtest/
srwx------ 1 ubuntu ubuntu    0 Mar  6 23:11 rpc_socket=

Do you have a better way of connecting to a test node during qa testing?

@laanwj
Copy link
Member Author

laanwj commented Mar 7, 2017

With this PR, I'm not able to do that anymore because the conf file doesn't contain the username/password, and even when I add them back in, I get the error:

Right, I could change it to add the options (including -rpcconnect) to the bitcoin.conf.

And even when I add them back in, I get the error:

The above should work, though. That's exactly how I've been testing it. The connection failure is strange. Did you patch libevent so that bitcoin-cli is able to connect to UNIX sockets? (you did add LIBEVENT_EXPERIMENTAL otherwise you'd be getting a RPC was asked to connect to a UNIX socket. However, the version of libevent that this utility was compiled with has no support for that. error when specifying :unix:)

Do you have a better way of connecting to a test node during qa testing?

I'll add an environment variable to force running the RPC tests over TCP.

@laanwj
Copy link
Member Author

laanwj commented Mar 7, 2017

@jnewbery Okay, added two commits:

  • add BITCOIN_TEST_RPC_TCP environment variable to force tests to use TCP instead of UNIX sockets for RPC
  • write connection info to bitcoin.conf instead of providing it on the command line. -conf should just work, also for UNIX sockets, if your client supports it.

@laanwj laanwj force-pushed the 2017_03_unix_socket branch 2 times, most recently from 5e20ec3 to 681d73c Compare March 9, 2017 08:23
@laanwj
Copy link
Member Author

laanwj commented Mar 9, 2017

Needed rebase, so rebased and squashed the squasme: commits.

@jonasschnelli
Copy link
Contributor

F.I.Y:
I wanted to test this via binaries built in gitian.
I could start the daemon (wanted to test in conjunction with Q): ./bitcoin-0.14.99/bin/bitcoin-qt -debug=rpc -debug=http -rpcbind=nix:socket --regtest

However the CLI responded with:
error: RPC was asked to connect to a UNIX socket. However, the version of libevent that this utility was compiled with has no support for that.

Maybe we should bump libevent in the dependencies directory?

@laanwj
Copy link
Member Author

laanwj commented Mar 9, 2017

However the CLI responded with:

Yes, that's expected. bitcoin-cli support needs this patch: https://github.com/laanwj/libevent/commit/403b793771341460b262edb1abc31fae267174bd.patch as well as defining -DLIBEVENT_EXPERIMENTAL

Without, you should still be able to use the socket with other clients, for example from Python or with curl. The server-side support is not dependent on that patch.

This could be done as part of the depends system, but rather not in this PR as it is a wholly separate discussion.

@laanwj laanwj added this to the 0.15.0 milestone Mar 10, 2017
@laanwj laanwj force-pushed the 2017_03_unix_socket branch 2 times, most recently from 8394db2 to 2dd6914 Compare March 12, 2017 12:06
@laanwj
Copy link
Member Author

laanwj commented Mar 12, 2017

Rebased (RPC tests logging changes #9768)

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.
@JeremyRubin
Copy link
Contributor

Oops, I accidentally did my review of this code on the other PR. See here #9979 (review)

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 695e854, though code is pretty out of date

if (!sockaddr_from_path(&addr, path)) {
return -1;
}
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "rpc: UNIX sockets support"

Maybe add a sock_from_path function to dedup code between evunix_bind_fd & evunix_connect_fd a little more.

addr.sun_family = AF_UNIX; ]])],
[ AC_MSG_RESULT(yes);
AC_DEFINE(HAVE_SOCKADDR_UN, 1,[Define this symbol if the sockaddr_un is available])
AM_CONDITIONAL([BUILD_EVUNIX],[true])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "rpc: UNIX sockets support"

Naming of BUILD_EVUNIX seems a little unusual. Would expect something more like ENABLE_UNIX_SOCKETS (to be similar to ENABLE_WALLET, ENABLE_QT, ENABLE_ZMQ, etc).

Maybe more to the point though, since BUILD_EVUNIX is only used to control building of a single source file, maybe consider not defining it as build variable at all and just using #if HAVE_SOCKADDR_UN in the c++ source. I think this makes sense since you are already using HAVE_SOCKADDR_UN to avoid calling evunix functions, so you might as well use the same variable to avoid defining those functions.


// If path is not complete, it is interpreted relative to the data directory.
if (!name.is_complete()) {
name = GetDataDir(true) / name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "rpc: UNIX sockets support"

Maybe consider deduping the path manipulation code with the same code in bitcoin-cli

/* Special: will get here for UNIX sockets. As we have no way to indicate that,
* just pass localhost IPv6.
*/
address = "::1";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "rpc: UNIX sockets support"

Seems like this instead of emulating an NET_IPV6 address, this should have it's own network type in netaddress.h similar to NET_TOR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. This seems like another good use-case for NET_INTERNAL from #10446 (or something similar). As a bonus, that would allow us to use associative containers for unix socket addresses, since each path would have its own hash.

{
std::string name = path.string();
if (name.size() >= sizeof(addr->sun_path)) {
/* Name too long */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "rpc: UNIX sockets support"

Would it be possible to throw an exception here? Since the path comes from the user's configuration it seems like it would be better to provide a specific error about the path than a generic connect or bind error.

{
struct sockaddr_un peer_unix;
socklen_t peer_unix_len = sizeof(peer_unix);
if (getpeername(fd, (sockaddr*)&peer_unix, &peer_unix_len) == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "rpc: More robust localhost-UNIX detection"

Would it be better to use getsockname than getpeername for this? Seems like maybe that would work even on a socket that isn't connected.

'''
raise NotImplementedError

class RPCConnectInfoTCP:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "tests: Run RPC tests optionally over UNIX socket"

Probably should inherit from RPCConnectInfo. Same with RPCConnectInfoUNIX class below. Doesn't really affect anything, but might make generated documentation / help messages a little better.

"""
Args:
url (str): URL of the RPC server to call
node_number (int): the node number (or id) that this calls to
urlinfo (RPCConnectInfo): Connection info of the RPC server to call
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "tests: Run RPC tests optionally over UNIX socket"

s/urlinfo/conninfo/

@jnewbery
Copy link
Contributor

jnewbery commented May 9, 2017

I've had a quick read through the changes to the functional tests, but I'll defer doing a full review until this is rebased. A few general comments:

Copy link
Member

@theuni theuni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK. I added a few comments, and @ryanofsky beat me to several others.

@@ -377,6 +381,9 @@ bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPN

# bitcoin-cli binary #
bitcoin_cli_SOURCES = bitcoin-cli.cpp
if BUILD_EVUNIX
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is OK. I think we'll end up with a few wrappers for libevent, so moving those to their own internal helper lib will make sense.

/* Special: will get here for UNIX sockets. As we have no way to indicate that,
* just pass localhost IPv6.
*/
address = "::1";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. This seems like another good use-case for NET_INTERNAL from #10446 (or something similar). As a bonus, that would allow us to use associative containers for unix socket addresses, since each path would have its own hash.

close(fd);
return -1;
}
evutil_make_socket_nonblocking(fd);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to disable SIGPIPE here too?

#ifdef HAVE_SOCKADDR_UN
//! Clean up UNIX sockets on shutdown
for (const auto& path: cleanupUNIXSockets) {
evunix_remove_socket(path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably .clear() in case this gets called twice.

@laanwj
Copy link
Member Author

laanwj commented Jun 24, 2017

Closing this for now. I'll probably pick it up again for 0.16.

@Sjors
Copy link
Member

Sjors commented Oct 26, 2018

Concept ACK. c-lightning does this too FWIW.

This was briefly discussed during the meeting today. Tag as Up for Grabs?

@maflcko
Copy link
Member

maflcko commented Oct 30, 2019

Marked as "up for grabs"

@laanwj
Copy link
Member Author

laanwj commented Oct 30, 2019

The server side should be easy to pick up.
As for the client side I have opened a libevent issue to track this here: libevent/libevent#907
Unfortunately there is very little interest from their side for this.

@vinipsmaker
Copy link

Does it have to be HTTP? Would it be easier to implement if it was pure (line delimited) JSON-RPC over UNIX domain sockets?

@bitcoin bitcoin deleted a comment May 15, 2021
@michaelfolkson
Copy link
Contributor

Does it have to be HTTP? Would it be easier to implement if it was pure (line delimited) JSON-RPC over UNIX domain sockets?

This was discussed today on IRC. In summary, this is what c-lightning does for its UNIX socket support and what Core could potentially do in future for UNIX socket support as an alternative approach to getting a PR merged in libevent. Any non HTTP based UNIX socket support would need to be in addition to the existing HTTP based JSON-RPC rather than as a replacement.

@ryanofsky
Copy link
Contributor

i don't see any problem personally with adding support for unix sockets rpc server / client that uses plain json-rpc lines, if someone wants to work on that

This seems ok, but without URLs, you would have to decide how to handle wallet endpoints. Maybe create different sockets for different wallets like <datadir>/wallets/<walletname>/rpc.sock, or use one socket and accept an extra wallet RPC param.

@JeremyRubin
Copy link
Contributor

Any non HTTP based UNIX socket support would need to be in addition to the existing HTTP based JSON-RPC rather than as a replacement.

contestable, i think it would be great to deprecate then remove http support & leave it up to external proxy services.

ghazel pushed a commit to clostra/libevent that referenced this pull request Mar 10, 2022
This patch adds functionality to pass a pre-existing connection
as a bufferevent to `evhttp_connection_base_bufferevent_new`.

When the bufferevent has an existing fd, the evcon starts
in state `EVCON_IDLE` so that requests can be made immediately.

I am using this in Bitcoin Core for doing HTTP requests over
a UNIX socket (bitcoin/bitcoin#9919).

It could also be useful in sandboxed environments such as cloudabi
that cannot directly make connections but can pass around fds.
@bitcoin bitcoin locked and limited conversation to collaborators Oct 30, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet