-
Notifications
You must be signed in to change notification settings - Fork 39.1k
bitcoin-cli: Add -ipcconnect option #32297
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
Changes from all commits
3cd1cd3
df76891
6a54834
8d614bf
0448a19
fbea576
4565cff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,24 +38,23 @@ static std::vector<std::vector<std::string>> g_rpcauth; | |
| static std::map<std::string, std::set<std::string>> g_rpc_whitelist; | ||
| static bool g_rpc_whitelist_default = false; | ||
|
|
||
| static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCRequest& jreq) | ||
| static UniValue JSONErrorReply(UniValue objError, const JSONRPCRequest& jreq, HTTPStatusCode& nStatus) | ||
| { | ||
| // Sending HTTP errors is a legacy JSON-RPC behavior. | ||
| // HTTP errors should never be returned if JSON-RPC v2 was requested. This | ||
| // function should only be called when a v1 request fails or when a request | ||
| // cannot be parsed, so the version is unknown. | ||
| Assume(jreq.m_json_version != JSONRPCVersion::V2); | ||
|
|
||
| // Send error reply from json-rpc error object | ||
| int nStatus = HTTP_INTERNAL_SERVER_ERROR; | ||
| nStatus = HTTP_INTERNAL_SERVER_ERROR; | ||
| int code = objError.find_value("code").getInt<int>(); | ||
|
|
||
| if (code == RPC_INVALID_REQUEST) | ||
| nStatus = HTTP_BAD_REQUEST; | ||
| else if (code == RPC_METHOD_NOT_FOUND) | ||
| nStatus = HTTP_NOT_FOUND; | ||
|
|
||
| std::string strReply = JSONRPCReplyObj(NullUniValue, std::move(objError), jreq.id, jreq.m_json_version).write() + "\n"; | ||
|
|
||
| req->WriteHeader("Content-Type", "application/json"); | ||
| req->WriteReply(nStatus, strReply); | ||
| return JSONRPCReplyObj(NullUniValue, std::move(objError), jreq.id, jreq.m_json_version); | ||
| } | ||
|
|
||
| //This function checks username and password against -rpcauth | ||
|
|
@@ -101,75 +100,37 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna | |
| return CheckUserAuthorized(user, pass); | ||
| } | ||
|
|
||
| static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) | ||
| UniValue ExecuteHTTPRPC(const UniValue& valRequest, JSONRPCRequest& jreq, HTTPStatusCode& status) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at this with fresh eyes after some time, and I'm wondering why to expose this to the interface instead of going directly to We shouldn't need HTTP status codes at all for RPC over IPC
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. re: #32297 (comment) Reason for calling the HTTP function instead of the JSONRPCExec is just that the HTTP function provides some additional functionality like user authorization and method whitelisting. I imagine in the future it could also provide more functionality like having different default wallets or different settings for different users. It would be reasonable for the IPC inteface to just expose the JSON-RPC methods without any higher level functionality, but since the IPC interface is used by |
||
| { | ||
| // JSONRPC handles only POST | ||
| if (req->GetRequestMethod() != HTTPRequest::POST) { | ||
| req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests"); | ||
| return false; | ||
| } | ||
| // Check authorization | ||
|
ryanofsky marked this conversation as resolved.
|
||
| std::pair<bool, std::string> authHeader = req->GetHeader("authorization"); | ||
| if (!authHeader.first) { | ||
| req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); | ||
| req->WriteReply(HTTP_UNAUTHORIZED); | ||
| return false; | ||
| } | ||
|
|
||
| JSONRPCRequest jreq; | ||
| jreq.context = context; | ||
| jreq.peerAddr = req->GetPeer().ToStringAddrPort(); | ||
| if (!RPCAuthorized(authHeader.second, jreq.authUser)) { | ||
| LogWarning("ThreadRPCServer incorrect password attempt from %s", jreq.peerAddr); | ||
|
|
||
| /* Deter brute-forcing | ||
| If this results in a DoS the user really | ||
| shouldn't have their RPC port exposed. */ | ||
| UninterruptibleSleep(std::chrono::milliseconds{250}); | ||
|
|
||
| req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); | ||
| req->WriteReply(HTTP_UNAUTHORIZED); | ||
| return false; | ||
| } | ||
|
|
||
| status = HTTP_OK; | ||
| try { | ||
| // Parse request | ||
| UniValue valRequest; | ||
| if (!valRequest.read(req->ReadBody())) | ||
| throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); | ||
|
|
||
| // Set the URI | ||
| jreq.URI = req->GetURI(); | ||
|
|
||
| UniValue reply; | ||
| bool user_has_whitelist = g_rpc_whitelist.contains(jreq.authUser); | ||
| if (!user_has_whitelist && g_rpc_whitelist_default) { | ||
| LogWarning("RPC User %s not allowed to call any methods", jreq.authUser); | ||
| req->WriteReply(HTTP_FORBIDDEN); | ||
| return false; | ||
| status = HTTP_FORBIDDEN; | ||
| return {}; | ||
|
|
||
| // singleton request | ||
| } else if (valRequest.isObject()) { | ||
| jreq.parse(valRequest); | ||
| if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].contains(jreq.strMethod)) { | ||
| LogWarning("RPC User %s not allowed to call method %s", jreq.authUser, jreq.strMethod); | ||
| req->WriteReply(HTTP_FORBIDDEN); | ||
| return false; | ||
| status = HTTP_FORBIDDEN; | ||
| return {}; | ||
| } | ||
|
|
||
| // Legacy 1.0/1.1 behavior is for failed requests to throw | ||
| // exceptions which return HTTP errors and RPC errors to the client. | ||
| // 2.0 behavior is to catch exceptions and return HTTP success with | ||
| // RPC errors, as long as there is not an actual HTTP server error. | ||
| const bool catch_errors{jreq.m_json_version == JSONRPCVersion::V2}; | ||
| reply = JSONRPCExec(jreq, catch_errors); | ||
|
|
||
| UniValue reply{JSONRPCExec(jreq, catch_errors)}; | ||
| if (jreq.IsNotification()) { | ||
| // Even though we do execute notifications, we do not respond to them | ||
| req->WriteReply(HTTP_NO_CONTENT); | ||
| return true; | ||
| status = HTTP_NO_CONTENT; | ||
| return {}; | ||
| } | ||
|
|
||
| return reply; | ||
| // array of requests | ||
| } else if (valRequest.isArray()) { | ||
| // Check authorization for each request's method | ||
|
|
@@ -183,15 +144,15 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) | |
| std::string strMethod = request.find_value("method").get_str(); | ||
| if (!g_rpc_whitelist[jreq.authUser].contains(strMethod)) { | ||
| LogWarning("RPC User %s not allowed to call method %s", jreq.authUser, strMethod); | ||
| req->WriteReply(HTTP_FORBIDDEN); | ||
| return false; | ||
| status = HTTP_FORBIDDEN; | ||
| return {}; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Execute each request | ||
| reply = UniValue::VARR; | ||
| UniValue reply = UniValue::VARR; | ||
| for (size_t i{0}; i < valRequest.size(); ++i) { | ||
| // Batches never throw HTTP errors, they are always just included | ||
| // in "HTTP OK" responses. Notifications never get any response. | ||
|
|
@@ -218,23 +179,70 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) | |
| // empty response in this case to favor being backwards compatible | ||
| // over complying with the JSON-RPC 2.0 spec in this case. | ||
| if (reply.size() == 0 && valRequest.size() > 0) { | ||
| req->WriteReply(HTTP_NO_CONTENT); | ||
| return true; | ||
| status = HTTP_NO_CONTENT; | ||
| return {}; | ||
| } | ||
| return reply; | ||
| } | ||
| else | ||
| throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); | ||
|
|
||
| req->WriteHeader("Content-Type", "application/json"); | ||
| req->WriteReply(HTTP_OK, reply.write() + "\n"); | ||
| } catch (UniValue& e) { | ||
| JSONErrorReply(req, std::move(e), jreq); | ||
| return false; | ||
| return JSONErrorReply(std::move(e), jreq, status); | ||
| } catch (const std::exception& e) { | ||
| JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq); | ||
| return false; | ||
| return JSONErrorReply(JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq, status); | ||
| } | ||
| } | ||
|
|
||
| static void HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) | ||
| { | ||
| // JSONRPC handles only POST | ||
| if (req->GetRequestMethod() != HTTPRequest::POST) { | ||
| req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests"); | ||
| return; | ||
| } | ||
| // Check authorization | ||
| std::pair<bool, std::string> authHeader = req->GetHeader("authorization"); | ||
| if (!authHeader.first) { | ||
| req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); | ||
| req->WriteReply(HTTP_UNAUTHORIZED); | ||
| return; | ||
| } | ||
|
|
||
| JSONRPCRequest jreq; | ||
| jreq.context = context; | ||
| jreq.peerAddr = req->GetPeer().ToStringAddrPort(); | ||
| jreq.URI = req->GetURI(); | ||
| if (!RPCAuthorized(authHeader.second, jreq.authUser)) { | ||
| LogWarning("ThreadRPCServer incorrect password attempt from %s", jreq.peerAddr); | ||
|
|
||
| /* Deter brute-forcing | ||
| If this results in a DoS the user really | ||
| shouldn't have their RPC port exposed. */ | ||
| UninterruptibleSleep(std::chrono::milliseconds{250}); | ||
|
|
||
| req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); | ||
| req->WriteReply(HTTP_UNAUTHORIZED); | ||
| return; | ||
| } | ||
|
|
||
| // Generate reply | ||
| HTTPStatusCode status; | ||
| UniValue reply; | ||
| UniValue request; | ||
| if (request.read(req->ReadBody())) { | ||
| reply = ExecuteHTTPRPC(request, jreq, status); | ||
| } else { | ||
| reply = JSONErrorReply(JSONRPCError(RPC_PARSE_ERROR, "Parse error"), jreq, status); | ||
| } | ||
|
|
||
| // Write reply | ||
| if (reply.isNull()) { | ||
| // Error case or no-content notification reply. | ||
| req->WriteReply(status); | ||
| } else { | ||
| req->WriteHeader("Content-Type", "application/json"); | ||
| req->WriteReply(status, reply.write() + "\n"); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| static bool InitRPCAuthentication() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -719,7 +719,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) | |
| argsman.AddArg("-rpcworkqueue=<n>", strprintf("Set the maximum depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); | ||
| argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); | ||
| if (can_listen_ipc) { | ||
| argsman.AddArg("-ipcbind=<address>", "Bind to Unix socket address and listen for incoming connections. Valid address values are \"unix\" to listen on the default path, <datadir>/node.sock, or \"unix:/custom/path\" to specify a custom path. Can be specified multiple times to listen on multiple paths. Default behavior is not to listen on any path. If relative paths are specified, they are interpreted relative to the network data directory. If paths include any parent directory components and the parent directories do not exist, they will be created.", ArgsManager::ALLOW_ANY, OptionsCategory::IPC); | ||
| argsman.AddArg("-ipcbind=<address>", "Bind to Unix socket address and listen for incoming connections. Valid address values are \"unix\" to listen on the default path, <datadir>/node.sock, or \"unix:/custom/path\" to specify a custom path. Can be specified multiple times to listen on multiple paths. Default behavior is not to listen on any path. If relative paths are specified, they are interpreted relative to the network data directory. If paths include any parent directory components and the parent directories do not exist, they will be created. Enabling this gives local processes that can access the socket unauthenticated RPC access, so it's important to choose a path with secure permissions if customizing this.", ArgsManager::ALLOW_ANY, OptionsCategory::IPC); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be doing something wrong here, but I am not seeing this help message in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. re: #32297 (comment) Nice find. The current behavior is expected but pointlessly confusing. The goal of this code is to show The problem is the --- a/src/bitcoin.cpp
+++ b/src/bitcoin.cpp
@@ -168,7 +168,7 @@ bool UseMultiprocess(const CommandLine& cmd)
// If any -ipc* options are set these need to be processed by a
// multiprocess-capable binary.
- return args.IsArgSet("-ipcbind") || args.IsArgSet("-ipcconnect") || args.IsArgSet("-ipcfd");
+ return args.IsArgSet("-ipcbind") || args.IsArgSet("-ipcconnect") || args.IsArgSet("-ipcfd") || HelpRequested(args);
}
//! Execute the specified bitcoind, bitcoin-qt or other command line in `args`
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh gotcha, |
||
| } | ||
|
|
||
| #if HAVE_DECL_FORK | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.