Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

IPv6 RPC using asynchronously accepted connections #457

Merged
merged 11 commits into from

6 participants

@muggenhor

The commits in this pull request modify the RPC-connection handling code in such a way that listening for and accepting of incoming connections is performed asynchronous (reading/writing is still synchronous).

This allows for listening on multiple sockets at once, which I use in one of the other commits to implement dual IPv4/IPv6 support.

@gavinandresen

I'm concerned this might make RPC code that implicitly assumes the RPC is single-threaded deadlock or crash. How much testing did you do-- has this been tested on an in-production, high-RPC-traffic server?

@muggenhor

It currently still is single-threaded, i.e. all code is still executed from ThreadRPCServer2 via the "io_service.run_one()" construct.

The way it basically works is that certain actions are started (using 'async_*' methods), and get passed along with them an event handler to be called upon that action's completion. The io_service object manages these actions, waits for any of them to complete then dispatches the appropriate event handler.

All of that happens only within the threads from which you call io_service.run(), io_service.run_one(), io_service.poll() or io_service.poll_one(). As a result this allows future expansion into multiple threads by simple invoking io_service.run() in a new thread, but doesn't inherently add more threads (just allows for it).

@muggenhor

PS The main reason for using asynchronous I/O is to allow binding to multiple addresses for RPC without requiring one thread for every socket. This is what enables a dual IPv4/IPv6 stack.

@gavinandresen

I don't know nuthin about IPv6/boost::asio stuff. General comment is it seems like this maybe should be part of a larger "support IPv6" branch.

@muggenhor

Well, all that can be supported about IPv6 for RPC is in this branch. So that's exactly what this branch is: a "support IPv6 for RPC" branch.

Given that the RPC code is completely separate from any other networking code it actually makes sense to migrate it separately. That's why I'm not even trying to support IPv6 across all of bitcoin at once, incremental changes tend to work better in my experience.

@muggenhor

I've rebased the branch against master to make it easy to merge in.

@luke-jr

This conflicts with threaded JSON-RPC which is needed by many people. Can you make an IPv6-only version?

@jgarzik
Owner

agree w/ first half of luke-jr's comment. second half... not sure we want an IPv6-only version?

@luke-jr

I meant a patch that only adds IPv6, without the conflicting async stuff.

@muggenhor

Adding IPv6 (or any other protocol that requires an additional listening socket) requires event-driven (aka asynchronous I/O). That or a separate thread per listening socket, wich conflicts with the RPC's assumption that RPC code is single threaded...

Additionally event-driven approaches tend to scale better (less context switching, locking and per-thread resources overhead).

@luke-jr

Can we do async for listening only, then? Threads are needed for actual RPC calls since some may block.

@muggenhor

Another (probably better) solution would be to have a select(2)-like event-based processing loop. It would have the single-thread advantage of asynchronous I/O but the simplicity of a callback-less design. As I assume that the addition of callbacks in my current implementation is what you like least? (Please confirm/deny that last question/statement.)

That should localise most of the changes to the place where current code calls listener.accept(socket). This solution I should be able to implement rather quickly in the weekend.

@luke-jr

I dislike the fact that a 'getwork' call will block all other JSON-RPC until it completes.

@muggenhor

Yes, but that is unrelated to my patch.

The alternate implementation of IPv6 support I'm thinking of would look somewhat like this pseudocode:

  • create listener sockets (IPv4 and IPv6)
  • asynchronously accept a connection on both listeners (acceptor's in Asio's terminology)
  • from the accept callbacks: place the newly connected socket in a queue, then start a new async accept op

the mainloop would then be this:

  • wait for a single event to occur (io_service.run_one())
  • handle all connections in the queue until queue is empty
  • restart loop

That approach would still have a single callback, but only to accept tue connection, not to handle it. If there are no objections to this approach I'll implement it this weekend.

@luke-jr

It's related, because your patch conflicts with it. Instead of conflicting, why not implement IPv6 RPC on top of the existing multithreaded JSON-RPC branch (#568)?

@muggenhor

The reason it conflicts with it is simple of course: there was no multithreaded RPC patch when I wrote this patch.

As for resolving those conflicts by implementing on top of #568, no promises but I'll look at it in the weekend. Right now I'm going to get some much needed sleep.

@muggenhor

Current branch is on top of #568. I've used the approach outlined above (using a connection queue).

@luke-jr

ACK: Tested fine for me in 'next-test'

@luke-jr

For some reason, if -rpcallowip is used, it sees local connections as ::ffff:127.0.0.1 and sends a 403 instead of allowing the connection.

(side note: #568 has been rebased)

@jgarzik
Owner

Request rebase on top of #1101... we certainly do want to support IPv6 RPC.

@muggenhor

@jgarzik I'll work on updating this pull request next Thursday (Ascension Day, national holiday so I'll have some time off).

@jgarzik
Owner

Thanks!

Note that pull #1101 is now upstream, and will be in upcoming version 0.7

@luke-jr

Please don't forget to fix the -rpcallowip issue.

@muggenhor

@luke-jr This also contains a change (in 652eebf08e7f0e32d686d4e36475742fa27f71cc) to treat IPv4-mapped IPv6 addresses (::127.0.0.1 is one) as IPv4 addresses.

src/bitcoinrpc.cpp
((6 lines not shown))
{
- if (strAddress == asio::ip::address_v4::loopback().to_string())
+ // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses
+ if (address.is_v6()
+ && (address.to_v6().is_v4_compatible()
+ || address.to_v6().is_v4_mapped()))
+ return ClientAllowed(address.to_v6().to_v4());
+
+ if (address == asio::ip::address_v4::loopback()
+ || address == asio::ip::address_v6::loopback())
@luke-jr
luke-jr added a note

This strikes me as flawed by design, though perhaps that's inherited from boost... does it not have any kind of "is loopback" method?
Even IPv4 has 16,777,216 unique loopback addresses.

No, it doesn't. As for IPv6, that only has a single loopback address. (::1/128).

And while I agree that that ^^ code doesn't address all loopback cases. It does address all loopback cases covered by the previous version of that code.

That being said, I'll gladly add another commit to improve the IPv4 case (using a netmask check against 127.0.0.0/8).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@muggenhor

I believe that this pull request is ready for merging.

muggenhor added some commits
@muggenhor muggenhor Use asynchronous I/O to handle RPC requests
This allows more flexibility in the RPC code, e.g. making it easier to
handle multiple simultaneous connections later on.

Currently asynchronous I/O is only used to listen for and accept
incoming connections.  Asynchronous reading/writing is more involved.

Signed-off-by: Giel van Schijndel <me@mortis.eu>
914dc01
@muggenhor muggenhor Add dual IPv4/IPv6 stack support to the RPC server
The RPC server now listens for, and handles, incoming connections on
both IPv4 as well as IPv6.

If available (and usable) it uses a dual IPv4/IPv6 socket on systems
that support it (e.g. Linux and BSDs) and falls back to separate
IPv4/IPv6 sockets on systems that don't (e.g. Windows).

Signed-off-by: Giel van Schijndel <me@mortis.eu>
c1ecab8
@muggenhor muggenhor Allow clients on the IPv6 loopback as well
Signed-off-by: Giel van Schijndel <me@mortis.eu>
43b6daf
@muggenhor muggenhor Generalise RPC connection handling code to allow more listening sockets
Using this modification it should be relatively easy to, at a later
time, listen on multiple addresses (even Unix domain sockets should be
possible).

Signed-off-by: Giel van Schijndel <me@mortis.eu>
a0780ba
@muggenhor muggenhor Allow all addresses on the loopback subnet (127.0.0.0/8) not just 127…
….0.0.1

Signed-off-by: Giel van Schijndel <me@mortis.eu>
7cc2cea
@muggenhor muggenhor Use the QueueShutdown signal to stop accepting new RPC connections
Signed-off-by: Giel van Schijndel <me@mortis.eu>
fbf9df2
@sipa
Owner

ACK

muggenhor added some commits
@muggenhor muggenhor Merge branch 'master' into async-ipv6-rpc
Conflicts:
	src/bitcoinrpc.cpp

Signed-off-by: Giel van Schijndel <me@mortis.eu>
07368a9
@muggenhor muggenhor *Always* send a shutdown signal to enable custom shutdown actions
NOTE: This is required to be sure that we can properly shut down the RPC
      thread.

Signed-off-by: Giel van Schijndel <me@mortis.eu>
896899e
@Diapolo

NACK until the last commit is clarified.

@muggenhor Wait, what are you doing there to the shutdown ... we had a long discussion and merged a patch a few days ago. Your last commit is likely to break sth. or at least change the current behaviour once more, see #1439.

@luke-jr

basic_socket_acceptor needs -lmswsock added to Windows builds:

  • bitcoin-qt.pro
  • src/makefile.linux-mingw
  • src/makefile.mingw
@Diapolo

Did anyone mind reading my comment above lukes...? I'm sure the last commit can cause trouble.

@muggenhor

@luke-jr it's been long since I've done windows development, but don't you mean ws2_32 ? And isn't that linked to already?

@Diapolo yes, I did read your comment. I however have a day job which doesn't leave me much time during the week to reply immediately. So being patient enough to wait till the next weekend following your comment might be nice.

Then as for the actual content of your comment:

Your last commit is likely to break sth

Please elaborate, because I've carefully checked how my change would affect existing code. As far as I could see there should be no difference except the location from where the shutdown thread gets started.

or at least change the current behaviour once more

As explained above: shutdown behaviour should not be changed for existing/untouched code. It should only affect the termination of the RPC handling's shutdown sequence.

I.e. the RPC code needs to be interrupted by a signal in order to terminate it. Setting a variable that can be polled (fShutdown) isn't enough because we're blocking until some kind of event (network I/O or internal operation on io_service or one of the sockets) occurs.

@Diapolo

@muggenhor I didn't want to hurry you the feedback of another dev would have been sufficient, too. I didn't want to offend you. That said StartShutdown() is currently used in bitcoinrpc, main, net and test_bitcoin.

GUI:

StartShutdown() -> uiInterface.QueueShutdown() -> quit() for QCoreApplication (Qt event loop) -> Shutdown(NULL); in bitcoin.cpp (no exit here) -> return 0; (Bitcoin-Qt exit)

NOUI:
StartShutdown() -> CreateThread(Shutdown, NULL); -> Shutdown(NULL) -> exit(0);

What happens if StartShutdown is called in e.g. net.cpp with your patch using the NOUI version? Perhaps you can explain to me the new flow with your patch for the NOUI version. I'm not that advanced with the boost signal thing ;). Just want to ensure nothing get's broken.

@muggenhor

@Diapolo as you correctly seem to have noticed nothing is changed for the GUI case (outside of the RPC code).

For the NOUI case the flow is changed to:
StartShutdown() -> raise QueueShutdown() signal -> CreateThread(Shutdown, NULL); -> Shutdown(NULL) -> exit(0);

In addition to that, for both GUI/NOUI the RPC code now uses the QueueShutdown() signal to stop listening for new connections:
QueueShutdown() signal -> for each listening socket as acceptor -> acceptor.cancel().

As for the CreateThread call. That's registered with the QueueShutdown signal, so will get called immediately (along with other signal handlers) when the signal is raised/emitted/sent. (calling a signal is done by an immediate for-loop on all slots/handlers).

@sipa
Owner

It does seem to simplify the shutdown code. @Diapolo: any reason to assume things will break with this patch?

Both bitcoin-qt and bitcoind seem to shutdown fine with this, via RPC stop, UI quit, or SIGINT.

@luke-jr

@muggenhor basic_socket_acceptor uses AcceptEx, which is defined in mswsock

@muggenhor

@luke-jr basic_socket_acceptor is already used in mainline, so the problem should exist already. Regardless, I've fixed it as well in my branch.

muggenhor added some commits
@muggenhor muggenhor Cancel outstanding listen ops for RPC when shutting down
Use Boost's signal2 slot tracking mechanism to cancel any (still open)
listening sockets when receiving a shutdown signal.

Signed-off-by: Giel van Schijndel <me@mortis.eu>
ad25804
@muggenhor muggenhor On Windows link with `mswsock`, it being required (indirectly) by RPC…
… code

Signed-off-by: Giel van Schijndel <me@mortis.eu>
5b14622
@Diapolo

I have no further doubts after reading the explanations above. Thanks for clarification!

@sipa sipa merged commit 4a52c18 into bitcoin:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 25, 2012
  1. @muggenhor

    Use asynchronous I/O to handle RPC requests

    muggenhor authored
    This allows more flexibility in the RPC code, e.g. making it easier to
    handle multiple simultaneous connections later on.
    
    Currently asynchronous I/O is only used to listen for and accept
    incoming connections.  Asynchronous reading/writing is more involved.
    
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
  2. @muggenhor

    Add dual IPv4/IPv6 stack support to the RPC server

    muggenhor authored
    The RPC server now listens for, and handles, incoming connections on
    both IPv4 as well as IPv6.
    
    If available (and usable) it uses a dual IPv4/IPv6 socket on systems
    that support it (e.g. Linux and BSDs) and falls back to separate
    IPv4/IPv6 sockets on systems that don't (e.g. Windows).
    
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
  3. @muggenhor

    Allow clients on the IPv6 loopback as well

    muggenhor authored
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
  4. @muggenhor

    Generalise RPC connection handling code to allow more listening sockets

    muggenhor authored
    Using this modification it should be relatively easy to, at a later
    time, listen on multiple addresses (even Unix domain sockets should be
    possible).
    
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
  5. @muggenhor

    Allow all addresses on the loopback subnet (127.0.0.0/8) not just 127…

    muggenhor authored
    ….0.0.1
    
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
  6. @muggenhor

    Use the QueueShutdown signal to stop accepting new RPC connections

    muggenhor authored
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
Commits on Jun 17, 2012
  1. @muggenhor

    Merge branch 'master' into async-ipv6-rpc

    muggenhor authored
    Conflicts:
    	src/bitcoinrpc.cpp
    
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
  2. @muggenhor

    *Always* send a shutdown signal to enable custom shutdown actions

    muggenhor authored
    NOTE: This is required to be sure that we can properly shut down the RPC
          thread.
    
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
Commits on Jun 24, 2012
  1. @muggenhor
  2. @muggenhor

    Cancel outstanding listen ops for RPC when shutting down

    muggenhor authored
    Use Boost's signal2 slot tracking mechanism to cancel any (still open)
    listening sockets when receiving a shutdown signal.
    
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
  3. @muggenhor

    On Windows link with `mswsock`, it being required (indirectly) by RPC…

    muggenhor authored
    … code
    
    Signed-off-by: Giel van Schijndel <me@mortis.eu>
This page is out of date. Refresh to see the latest.
View
2  bitcoin-qt.pro
@@ -306,7 +306,7 @@ isEmpty(BOOST_INCLUDE_PATH) {
macx:BOOST_INCLUDE_PATH = /opt/local/include
}
-windows:LIBS += -lws2_32 -lshlwapi
+windows:LIBS += -lws2_32 -lshlwapi -lmswsock
windows:DEFINES += WIN32
windows:RC_FILE = src/qt/res/bitcoin-qt.rc
View
266 src/bitcoinrpc.cpp
@@ -15,14 +15,18 @@
#undef printf
#include <boost/asio.hpp>
+#include <boost/asio/ip/v6_only.hpp>
+#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
+#include <boost/foreach.hpp>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/filesystem/fstream.hpp>
-typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> SSLStream;
+#include <boost/shared_ptr.hpp>
+#include <list>
#define printf OutputDebugStringF
// MinGW 3.4.5 gets "fatal error: had to relocate PCH" if the json headers are
@@ -2579,10 +2583,22 @@ void ErrorReply(std::ostream& stream, const Object& objError, const Value& id)
stream << HTTPReply(nStatus, strReply, false) << std::flush;
}
-bool ClientAllowed(const string& strAddress)
+bool ClientAllowed(const boost::asio::ip::address& address)
{
- if (strAddress == asio::ip::address_v4::loopback().to_string())
+ // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses
+ if (address.is_v6()
+ && (address.to_v6().is_v4_compatible()
+ || address.to_v6().is_v4_mapped()))
+ return ClientAllowed(address.to_v6().to_v4());
+
+ if (address == asio::ip::address_v4::loopback()
+ || address == asio::ip::address_v6::loopback()
+ || (address.is_v4()
+ // Chech whether IPv4 addresses match 127.0.0.0/8 (loopback subnet)
+ && (address.to_v4().to_ulong() & 0xff000000) == 0x7f000000))
return true;
+
+ const string strAddress = address.to_string();
const vector<string>& vAllow = mapMultiArgs["-rpcallowip"];
BOOST_FOREACH(string strAllow, vAllow)
if (WildcardMatch(strAddress, strAllow))
@@ -2593,9 +2609,10 @@ bool ClientAllowed(const string& strAddress)
//
// IOStream device that speaks SSL but can also speak non-SSL
//
+template <typename Protocol>
class SSLIOStreamDevice : public iostreams::device<iostreams::bidirectional> {
public:
- SSLIOStreamDevice(SSLStream &streamIn, bool fUseSSLIn) : stream(streamIn)
+ SSLIOStreamDevice(asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn)
{
fUseSSL = fUseSSLIn;
fNeedHandshake = fUseSSLIn;
@@ -2639,21 +2656,54 @@ class SSLIOStreamDevice : public iostreams::device<iostreams::bidirectional> {
private:
bool fNeedHandshake;
bool fUseSSL;
- SSLStream& stream;
+ asio::ssl::stream<typename Protocol::socket>& stream;
};
class AcceptedConnection
{
- public:
- SSLStream sslStream;
- SSLIOStreamDevice d;
- iostreams::stream<SSLIOStreamDevice> stream;
+public:
+ virtual ~AcceptedConnection() {}
+
+ virtual std::iostream& stream() = 0;
+ virtual std::string peer_address_to_string() const = 0;
+ virtual void close() = 0;
+};
+
+template <typename Protocol>
+class AcceptedConnectionImpl : public AcceptedConnection
+{
+public:
+ AcceptedConnectionImpl(
+ asio::io_service& io_service,
+ ssl::context &context,
+ bool fUseSSL) :
+ sslStream(io_service, context),
+ _d(sslStream, fUseSSL),
+ _stream(_d)
+ {
+ }
+
+ virtual std::iostream& stream()
+ {
+ return _stream;
+ }
+
+ virtual std::string peer_address_to_string() const
+ {
+ return peer.address().to_string();
+ }
+
+ virtual void close()
+ {
+ _stream.close();
+ }
- ip::tcp::endpoint peer;
+ typename Protocol::endpoint peer;
+ asio::ssl::stream<typename Protocol::socket> sslStream;
- AcceptedConnection(asio::io_service &io_service, ssl::context &context,
- bool fUseSSL) : sslStream(io_service, context), d(sslStream, fUseSSL),
- stream(d) { ; }
+private:
+ SSLIOStreamDevice<Protocol> _d;
+ iostreams::stream< SSLIOStreamDevice<Protocol> > _stream;
};
void ThreadRPCServer(void* parg)
@@ -2675,6 +2725,82 @@ void ThreadRPCServer(void* parg)
printf("ThreadRPCServer exited\n");
}
+// Forward declaration required for RPCListen
+template <typename Protocol, typename SocketAcceptorService>
+static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
+ ssl::context& context,
+ bool fUseSSL,
+ AcceptedConnection* conn,
+ const boost::system::error_code& error);
+
+/**
+ * Sets up I/O resources to accept and handle a new connection.
+ */
+template <typename Protocol, typename SocketAcceptorService>
+static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
+ ssl::context& context,
+ const bool fUseSSL)
+{
+ // Accept connection
+ AcceptedConnectionImpl<Protocol>* conn = new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL);
+
+ acceptor->async_accept(
+ conn->sslStream.lowest_layer(),
+ conn->peer,
+ boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>,
+ acceptor,
+ boost::ref(context),
+ fUseSSL,
+ conn,
+ boost::asio::placeholders::error));
+}
+
+/**
+ * Accept and handle incoming connection.
+ */
+template <typename Protocol, typename SocketAcceptorService>
+static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
+ ssl::context& context,
+ const bool fUseSSL,
+ AcceptedConnection* conn,
+ const boost::system::error_code& error)
+{
+ vnThreadsRunning[THREAD_RPCLISTENER]++;
+
+ // Immediately start accepting new connections, except when we're canceled or our socket is closed.
+ if (error != error::operation_aborted
+ && acceptor->is_open())
+ RPCListen(acceptor, context, fUseSSL);
+
+ AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn);
+
+ // TODO: Actually handle errors
+ if (error)
+ {
+ delete conn;
+ }
+
+ // Restrict callers by IP. It is important to
+ // do this before starting client thread, to filter out
+ // certain DoS and misbehaving clients.
+ else if (tcp_conn
+ && !ClientAllowed(tcp_conn->peer.address()))
+ {
+ // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake.
+ if (!fUseSSL)
+ conn->stream() << HTTPReply(403, "", false) << std::flush;
+ delete conn;
+ }
+
+ // start HTTP client thread
+ else if (!CreateThread(ThreadRPCServer3, conn)) {
+ printf("Failed to create RPC server client thread\n");
+ delete conn;
+ }
+
+ vnThreadsRunning[THREAD_RPCLISTENER]--;
+}
+
void ThreadRPCServer2(void* parg)
{
printf("ThreadRPCServer started\n");
@@ -2704,26 +2830,9 @@ void ThreadRPCServer2(void* parg)
return;
}
- bool fUseSSL = GetBoolArg("-rpcssl");
- asio::ip::address bindAddress = mapArgs.count("-rpcallowip") ? asio::ip::address_v4::any() : asio::ip::address_v4::loopback();
+ const bool fUseSSL = GetBoolArg("-rpcssl");
asio::io_service io_service;
- ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", 8332));
- ip::tcp::acceptor acceptor(io_service);
- try
- {
- acceptor.open(endpoint.protocol());
- acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
- acceptor.bind(endpoint);
- acceptor.listen(socket_base::max_connections);
- }
- catch(boost::system::system_error &e)
- {
- uiInterface.ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()),
- _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL);
- StartShutdown();
- return;
- }
ssl::context context(io_service, ssl::context::sslv23);
if (fUseSSL)
@@ -2744,39 +2853,60 @@ void ThreadRPCServer2(void* parg)
SSL_CTX_set_cipher_list(context.impl(), strCiphers.c_str());
}
- loop
+ // Try a dual IPv6/IPv4 socket, falling back to separate IPv4 and IPv6 sockets
+ const bool loopback = !mapArgs.count("-rpcallowip");
+ asio::ip::address bindAddress = loopback ? asio::ip::address_v6::loopback() : asio::ip::address_v6::any();
+ ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", 8332));
+
+ try
{
- // Accept connection
- AcceptedConnection *conn =
- new AcceptedConnection(io_service, context, fUseSSL);
+ boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service));
+ acceptor->open(endpoint.protocol());
+ acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
- vnThreadsRunning[THREAD_RPCLISTENER]--;
- acceptor.accept(conn->sslStream.lowest_layer(), conn->peer);
- vnThreadsRunning[THREAD_RPCLISTENER]++;
+ // Try making the socket dual IPv6/IPv4 (if listening on the "any" address)
+ boost::system::error_code v6_only_error;
+ acceptor->set_option(boost::asio::ip::v6_only(loopback), v6_only_error);
- if (fShutdown)
- {
- delete conn;
- return;
- }
+ acceptor->bind(endpoint);
+ acceptor->listen(socket_base::max_connections);
- // Restrict callers by IP. It is important to
- // do this before starting client thread, to filter out
- // certain DoS and misbehaving clients.
- if (!ClientAllowed(conn->peer.address().to_string()))
- {
- // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake.
- if (!fUseSSL)
- conn->stream << HTTPReply(403, "", false) << std::flush;
- delete conn;
- }
+ RPCListen(acceptor, context, fUseSSL);
+ // Cancel outstanding listen-requests for this acceptor when shutting down
+ uiInterface.QueueShutdown.connect(signals2::slot<void ()>(
+ static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get())
+ .track(acceptor));
- // start HTTP client thread
- else if (!CreateThread(ThreadRPCServer3, conn)) {
- printf("Failed to create RPC server client thread\n");
- delete conn;
+ // If dual IPv6/IPv4 failed (or we're opening loopback interfaces only), open IPv4 separately
+ if (loopback || v6_only_error)
+ {
+ bindAddress = loopback ? asio::ip::address_v4::loopback() : asio::ip::address_v4::any();
+ endpoint.address(bindAddress);
+
+ acceptor.reset(new ip::tcp::acceptor(io_service));
+ acceptor->open(endpoint.protocol());
+ acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
+ acceptor->bind(endpoint);
+ acceptor->listen(socket_base::max_connections);
+
+ RPCListen(acceptor, context, fUseSSL);
+ // Cancel outstanding listen-requests for this acceptor when shutting down
+ uiInterface.QueueShutdown.connect(signals2::slot<void ()>(
+ static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get())
+ .track(acceptor));
}
}
+ catch(boost::system::system_error &e)
+ {
+ uiInterface.ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()),
+ _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL);
+ StartShutdown();
+ return;
+ }
+
+ vnThreadsRunning[THREAD_RPCLISTENER]--;
+ io_service.run();
+ vnThreadsRunning[THREAD_RPCLISTENER]++;
}
void ThreadRPCServer3(void* parg)
@@ -2789,7 +2919,7 @@ void ThreadRPCServer3(void* parg)
loop {
if (fShutdown || !fRun)
{
- conn->stream.close();
+ conn->close();
delete conn;
--vnThreadsRunning[THREAD_RPCHANDLER];
return;
@@ -2797,24 +2927,24 @@ void ThreadRPCServer3(void* parg)
map<string, string> mapHeaders;
string strRequest;
- ReadHTTP(conn->stream, mapHeaders, strRequest);
+ ReadHTTP(conn->stream(), mapHeaders, strRequest);
// Check authorization
if (mapHeaders.count("authorization") == 0)
{
- conn->stream << HTTPReply(401, "", false) << std::flush;
+ conn->stream() << HTTPReply(401, "", false) << std::flush;
break;
}
if (!HTTPAuthorized(mapHeaders))
{
- printf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer.address().to_string().c_str());
+ printf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string().c_str());
/* Deter brute-forcing short passwords.
If this results in a DOS the user really
shouldn't have their RPC port exposed.*/
if (mapArgs["-rpcpassword"].size() < 20)
Sleep(250);
- conn->stream << HTTPReply(401, "", false) << std::flush;
+ conn->stream() << HTTPReply(401, "", false) << std::flush;
break;
}
if (mapHeaders["connection"] == "close")
@@ -2856,16 +2986,16 @@ void ThreadRPCServer3(void* parg)
// Send reply
string strReply = JSONRPCReply(result, Value::null, id);
- conn->stream << HTTPReply(200, strReply, fRun) << std::flush;
+ conn->stream() << HTTPReply(200, strReply, fRun) << std::flush;
}
catch (Object& objError)
{
- ErrorReply(conn->stream, objError, id);
+ ErrorReply(conn->stream(), objError, id);
break;
}
catch (std::exception& e)
{
- ErrorReply(conn->stream, JSONRPCError(-32700, e.what()), id);
+ ErrorReply(conn->stream(), JSONRPCError(-32700, e.what()), id);
break;
}
}
@@ -2917,9 +3047,9 @@ Object CallRPC(const string& strMethod, const Array& params)
asio::io_service io_service;
ssl::context context(io_service, ssl::context::sslv23);
context.set_options(ssl::context::no_sslv2);
- SSLStream sslStream(io_service, context);
- SSLIOStreamDevice d(sslStream, fUseSSL);
- iostreams::stream<SSLIOStreamDevice> stream(d);
+ asio::ssl::stream<asio::ip::tcp::socket> sslStream(io_service, context);
+ SSLIOStreamDevice<asio::ip::tcp> d(sslStream, fUseSSL);
+ iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > stream(d);
if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", "8332")))
throw runtime_error("couldn't connect to server");
View
11 src/init.cpp
@@ -9,6 +9,7 @@
#include "init.h"
#include "util.h"
#include "ui_interface.h"
+#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/convenience.hpp>
@@ -40,13 +41,8 @@ void ExitTimeout(void* parg)
void StartShutdown()
{
-#ifdef QT_GUI
// ensure we leave the Qt main loop for a clean GUI exit (Shutdown() is called in bitcoin.cpp afterwards)
uiInterface.QueueShutdown();
-#else
- // Without UI, Shutdown() can simply be started in a new thread
- CreateThread(Shutdown, NULL);
-#endif
}
void Shutdown(void* parg)
@@ -154,6 +150,11 @@ bool AppInit(int argc, char* argv[])
exit(ret);
}
+ // Create the shutdown thread when receiving a shutdown signal
+ boost::signals2::scoped_connection do_stop(
+ uiInterface.QueueShutdown.connect(boost::bind(
+ &CreateThread, &Shutdown, static_cast<void*>(0), false)));
+
fRet = AppInit2();
}
catch (std::exception& e) {
View
2  src/makefile.linux-mingw
@@ -39,7 +39,7 @@ ifdef USE_UPNP
DEFS += -DSTATICLIB -DUSE_UPNP=$(USE_UPNP)
endif
-LIBS += -l mingwthrd -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l shlwapi
+LIBS += -l mingwthrd -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l mswsock -l shlwapi
# TODO: make the mingw builds smarter about dependencies, like the linux/osx builds are
HEADERS = $(wildcard *.h)
View
2  src/makefile.mingw
@@ -36,7 +36,7 @@ ifdef USE_UPNP
DEFS += -DSTATICLIB -DUSE_UPNP=$(USE_UPNP)
endif
-LIBS += -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l shlwapi
+LIBS += -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l mswsock -l shlwapi
# TODO: make the mingw builds smarter about dependencies, like the linux/osx builds are
HEADERS = $(wildcard *.h)
Something went wrong with that request. Please try again.