From 8a5ae3c7a98ba873596b615afca138ca727de599 Mon Sep 17 00:00:00 2001 From: Matt Quinn Date: Sat, 1 Aug 2015 10:41:21 -0700 Subject: [PATCH 01/28] Consolidate individual references to the current maximum peer connection value of 125 into a single constant declaration. --- src/init.cpp | 4 ++-- src/net.cpp | 2 +- src/net.h | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 66f7005eb..8dedcb099 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -355,7 +355,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-externalip=", _("Specify your own public address")); strUsage += HelpMessageOpt("-forcednsseed", strprintf(_("Always query for peer addresses via DNS lookup (default: %u)"), 0)); strUsage += HelpMessageOpt("-listen", _("Accept connections from outside (default: 1 if no -proxy or -connect)")); - strUsage += HelpMessageOpt("-maxconnections=", strprintf(_("Maintain at most connections to peers (default: %u)"), 125)); + strUsage += HelpMessageOpt("-maxconnections=", strprintf(_("Maintain at most connections to peers (default: %u)"), DEFAULT_MAX_PEER_CONNECTIONS)); strUsage += HelpMessageOpt("-maxreceivebuffer=", strprintf(_("Maximum per-connection receive buffer, *1000 bytes (default: %u)"), 5000)); strUsage += HelpMessageOpt("-maxsendbuffer=", strprintf(_("Maximum per-connection send buffer, *1000 bytes (default: %u)"), 1000)); strUsage += HelpMessageOpt("-onion=", strprintf(_("Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: %s)"), "-proxy")); @@ -832,7 +832,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Make sure enough file descriptors are available int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-whitebind"), 1); - nMaxConnections = GetArg("-maxconnections", 125); + nMaxConnections = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); int nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS); if (nFD < MIN_CORE_FILEDESCRIPTORS) diff --git a/src/net.cpp b/src/net.cpp index ed22cf324..36ecce248 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -78,7 +78,7 @@ static CNode* pnodeLocalHost = NULL; uint64_t nLocalHostNonce = 0; static std::vector vhListenSocket; CAddrMan addrman; -int nMaxConnections = 125; +int nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS; bool fAddressesInitialized = false; vector vNodes; diff --git a/src/net.h b/src/net.h index 720763506..81b1c2e73 100644 --- a/src/net.h +++ b/src/net.h @@ -61,6 +61,8 @@ static const bool DEFAULT_UPNP = false; static const size_t MAPASKFOR_MAX_SZ = MAX_INV_SZ; /** The maximum number of entries in setAskFor (larger due to getdata latency)*/ static const size_t SETASKFOR_MAX_SZ = 2 * MAX_INV_SZ; +/** The maximum number of peer connections to maintain. */ +static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125; unsigned int ReceiveFloodSize(); unsigned int SendBufferSize(); From eb5f63fe5827b66b9e0c2e29ee759e049507a176 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Tue, 25 Aug 2015 20:12:08 +0200 Subject: [PATCH 02/28] net: Automatically create hidden service, listen on Tor Starting with Tor version 0.2.7.1 it is possible, through Tor's control socket API, to create and destroy 'ephemeral' hidden services programmatically. https://stem.torproject.org/api/control.html#stem.control.Controller.create_ephemeral_hidden_service This means that if Tor is running (and proper authorization is available), bitcoin automatically creates a hidden service to listen on, without user manual configuration. This will positively affect the number of available .onion nodes. - When the node is started, connect to Tor through control socket - Send `ADD_ONION` command - First time: - Make it create a hidden service key - Save the key in the data directory for later usage - Make it redirect port 8333 to the local port 8333 (or whatever port we're listening on). - Keep control socket connection open for as long node is running. The hidden service will (by default) automatically go away when the connection is closed. --- src/Makefile.am | 2 + src/init.cpp | 9 + src/main.cpp | 2 + src/net.cpp | 1 + src/netbase.cpp | 5 +- src/netbase.h | 4 + src/torcontrol.cpp | 573 +++++++++++++++++++++++++++++++++++++++++++++ src/torcontrol.h | 19 ++ 8 files changed, 611 insertions(+), 4 deletions(-) create mode 100644 src/torcontrol.cpp create mode 100644 src/torcontrol.h diff --git a/src/Makefile.am b/src/Makefile.am index 33c1fba2d..9963f2858 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -164,6 +164,7 @@ BITCOIN_CORE_H = \ threadsafety.h \ timedata.h \ tinyformat.h \ + torcontrol.h \ txdb.h \ txmempool.h \ ui_interface.h \ @@ -227,6 +228,7 @@ libbitcoin_server_a_SOURCES = \ rpcserver.cpp \ script/sigcache.cpp \ timedata.cpp \ + torcontrol.cpp \ txdb.cpp \ txmempool.cpp \ validationinterface.cpp \ diff --git a/src/init.cpp b/src/init.cpp index 8dedcb099..edbaf0de9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -28,6 +28,7 @@ #include "script/standard.h" #include "scheduler.h" #include "txdb.h" +#include "torcontrol.h" #include "ui_interface.h" #include "util.h" #include "utilmoneystr.h" @@ -193,6 +194,7 @@ void Shutdown() #endif #endif StopNode(); + StopTorControl(); UnregisterNodeSignals(GetNodeSignals()); if (fFeeEstimatesInitialized) @@ -355,6 +357,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-externalip=", _("Specify your own public address")); strUsage += HelpMessageOpt("-forcednsseed", strprintf(_("Always query for peer addresses via DNS lookup (default: %u)"), 0)); strUsage += HelpMessageOpt("-listen", _("Accept connections from outside (default: 1 if no -proxy or -connect)")); + strUsage += HelpMessageOpt("-listenonion", strprintf(_("Automatically create Tor hidden service (default: %d)"), DEFAULT_LISTEN_ONION)); strUsage += HelpMessageOpt("-maxconnections=", strprintf(_("Maintain at most connections to peers (default: %u)"), DEFAULT_MAX_PEER_CONNECTIONS)); strUsage += HelpMessageOpt("-maxreceivebuffer=", strprintf(_("Maximum per-connection receive buffer, *1000 bytes (default: %u)"), 5000)); strUsage += HelpMessageOpt("-maxsendbuffer=", strprintf(_("Maximum per-connection send buffer, *1000 bytes (default: %u)"), 1000)); @@ -366,6 +369,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-proxyrandomize", strprintf(_("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)"), 1)); strUsage += HelpMessageOpt("-seednode=", _("Connect to a node to retrieve peer addresses, and disconnect")); strUsage += HelpMessageOpt("-timeout=", strprintf(_("Specify connection timeout in milliseconds (minimum: 1, default: %d)"), DEFAULT_CONNECT_TIMEOUT)); + strUsage += HelpMessageOpt("-torcontrol=:", strprintf(_("Tor control port to use if onion listening enabled (default: %s)"), DEFAULT_TOR_CONTROL)); #ifdef USE_UPNP #if USE_UPNP strUsage += HelpMessageOpt("-upnp", _("Use UPnP to map the listening port (default: 1 when listening and no -proxy)")); @@ -810,6 +814,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("%s: parameter interaction: -listen=0 -> setting -upnp=0\n", __func__); if (SoftSetBoolArg("-discover", false)) LogPrintf("%s: parameter interaction: -listen=0 -> setting -discover=0\n", __func__); + if (SoftSetBoolArg("-listenonion", false)) + LogPrintf("%s: parameter interaction: -listen=0 -> setting -listenonion=0\n", __func__); } if (mapArgs.count("-externalip")) { @@ -1614,6 +1620,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0); #endif + if (GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) + StartTorControl(threadGroup, scheduler); + StartNode(threadGroup, scheduler); // Monitor the chain, and alert if we get blocks much quicker or slower than expected diff --git a/src/main.cpp b/src/main.cpp index 438dcc425..ab91ee41e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4402,9 +4402,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, CAddress addr = GetLocalAddress(&pfrom->addr); if (addr.IsRoutable()) { + LogPrintf("ProcessMessages: advertizing address %s\n", addr.ToString()); pfrom->PushAddress(addr); } else if (IsPeerAddrLocalGood(pfrom)) { addr.SetIP(pfrom->addrLocal); + LogPrintf("ProcessMessages: advertizing address %s\n", addr.ToString()); pfrom->PushAddress(addr); } } diff --git a/src/net.cpp b/src/net.cpp index 36ecce248..e255bfab2 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -212,6 +212,7 @@ void AdvertizeLocal(CNode *pnode) } if (addrLocal.IsRoutable()) { + LogPrintf("AdvertizeLocal: advertizing address %s\n", addrLocal.ToString()); pnode->PushAddress(addrLocal); } } diff --git a/src/netbase.cpp b/src/netbase.cpp index 8ff621942..c88a227fd 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -227,10 +227,7 @@ bool LookupNumeric(const char *pszName, CService& addr, int portDefault) return Lookup(pszName, addr, portDefault, false); } -/** - * Convert milliseconds to a struct timeval for select. - */ -struct timeval static MillisToTimeval(int64_t nTimeout) +struct timeval MillisToTimeval(int64_t nTimeout) { struct timeval timeout; timeout.tv_sec = nTimeout / 1000; diff --git a/src/netbase.h b/src/netbase.h index 27f0eac2a..21ac0aa4c 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -203,5 +203,9 @@ std::string NetworkErrorString(int err); bool CloseSocket(SOCKET& hSocket); /** Disable or enable blocking-mode for a socket */ bool SetSocketNonBlocking(SOCKET& hSocket, bool fNonBlocking); +/** + * Convert milliseconds to a struct timeval for e.g. select. + */ +struct timeval MillisToTimeval(int64_t nTimeout); #endif // BITCOIN_NETBASE_H diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp new file mode 100644 index 000000000..bb72315c8 --- /dev/null +++ b/src/torcontrol.cpp @@ -0,0 +1,573 @@ +#include "torcontrol.h" +#include "utilstrencodings.h" +#include "net.h" +#include "util.h" +#include "init.h" // Just for ShutdownRequested + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +const std::string DEFAULT_TOR_CONTROL = "127.0.0.1:9051"; + +/****** Low-level TorControlConnection ********/ + +/** Reply from Tor, can be single or multi-line */ +class TorControlReply +{ +public: + TorControlReply() { Clear(); } + + int code; + std::vector lines; + + void Clear() + { + code = 0; + lines.clear(); + } +}; + +/** Low-level handling for Tor control connection. + * Speaks the SMTP-like protocol as defined in torspec/control-spec.txt + */ +class TorControlConnection +{ +public: + typedef boost::function ConnectionCB; + typedef boost::function ReplyHandlerCB; + + /** Create a new TorControlConnection. + */ + TorControlConnection(struct event_base *base); + ~TorControlConnection(); + + /** + * Connect to a Tor control port. + * target is address of the form host:port. + * connected is the handler that is called when connection is succesfully established. + * disconnected is a handler that is called when the connection is broken. + * Return true on success. + */ + bool Connect(const std::string &target, const ConnectionCB& connected, const ConnectionCB& disconnected); + + /** + * Disconnect from Tor control port. + */ + bool Disconnect(); + + /** Send a command, register a handler for the reply. + * A trailing CRLF is automatically added. + * Return true on success. + */ + bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler); + + /** Response handlers for async replies */ + boost::signals2::signal async_handler; +private: + /** Callback when ready for use */ + boost::function connected; + /** Callback when connection lost */ + boost::function disconnected; + /** Libevent event base */ + struct event_base *base; + /** Connection to control socket */ + struct bufferevent *b_conn; + /** Message being received */ + TorControlReply message; + /** Response handlers */ + std::deque reply_handlers; + + /** Libevent handlers: internal */ + static void readcb(struct bufferevent *bev, void *ctx); + static void eventcb(struct bufferevent *bev, short what, void *ctx); +}; + +TorControlConnection::TorControlConnection(struct event_base *base): + base(base), b_conn(0) +{ +} + +TorControlConnection::~TorControlConnection() +{ + if (b_conn) + bufferevent_free(b_conn); +} + +void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) +{ + TorControlConnection *self = (TorControlConnection*)ctx; + struct evbuffer *input = bufferevent_get_input(bev); + size_t n_read_out = 0; + char *line; + assert(input); + // If there is not a whole line to read, evbuffer_readln returns NULL + while((line = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF)) != NULL) + { + std::string s(line, n_read_out); + free(line); + if (s.size() < 4) // Short line + continue; + // (-|+| ) + self->message.code = atoi(s.substr(0,3).c_str()); + self->message.lines.push_back(s.substr(4)); + char ch = s[3]; // '-','+' or ' ' + if (ch == ' ') { + // Final line, dispatch reply and clean up + if (self->message.code >= 600) { + // Dispatch async notifications to async handler + // Synchronous and asynchronous messages are never interleaved + self->async_handler(*self, self->message); + } else { + if (!self->reply_handlers.empty()) { + // Invoke reply handler with message + self->reply_handlers.front()(*self, self->message); + self->reply_handlers.pop_front(); + } else { + LogPrintf("[tor] Received unexpected sync reply %i\n", self->message.code); + } + } + self->message.Clear(); + } + } +} + +void TorControlConnection::eventcb(struct bufferevent *bev, short what, void *ctx) +{ + TorControlConnection *self = (TorControlConnection*)ctx; + if (what & BEV_EVENT_CONNECTED) { + LogPrintf("[tor] Succesfully connected!\n"); + self->connected(*self); + } else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { + if (what & BEV_EVENT_ERROR) + LogPrintf("[tor] Error connecting to Tor control socket\n"); + else + LogPrintf("[tor] End of stream\n"); + self->Disconnect(); + self->disconnected(*self); + } +} + +bool TorControlConnection::Connect(const std::string &target, const ConnectionCB& connected, const ConnectionCB& disconnected) +{ + if (b_conn) + Disconnect(); + // Parse target address:port + struct sockaddr_storage connect_to_addr; + int connect_to_addrlen = sizeof(connect_to_addr); + if (evutil_parse_sockaddr_port(target.c_str(), + (struct sockaddr*)&connect_to_addr, &connect_to_addrlen)<0) { + perror("evutil_parse_sockaddr_port\n"); + return false; + } + + // Create a new socket, set up callbacks and enable notification bits + b_conn = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); + if (!b_conn) + return false; + bufferevent_setcb(b_conn, TorControlConnection::readcb, NULL, TorControlConnection::eventcb, this); + bufferevent_enable(b_conn, EV_READ|EV_WRITE); + this->connected = connected; + this->disconnected = disconnected; + + // Finally, connect to target + if (bufferevent_socket_connect(b_conn, (struct sockaddr*)&connect_to_addr, connect_to_addrlen) < 0) { + perror("bufferevent_socket_connect"); + return false; + } + return true; +} + +bool TorControlConnection::Disconnect() +{ + if (b_conn) + bufferevent_free(b_conn); + b_conn = 0; + return true; +} + +bool TorControlConnection::Command(const std::string &cmd, const ReplyHandlerCB& reply_handler) +{ + if (!b_conn) + return false; + struct evbuffer *buf = bufferevent_get_output(b_conn); + if (!buf) + return false; + evbuffer_add(buf, cmd.data(), cmd.size()); + evbuffer_add(buf, "\r\n", 2); + reply_handlers.push_back(reply_handler); + return true; +} + +/****** General parsing utilities ********/ + +/* Split reply line in the form 'AUTH METHODS=...' into a type + * 'AUTH' and arguments 'METHODS=...'. + */ +static std::pair SplitTorReplyLine(const std::string &s) +{ + size_t ptr=0; + std::string type; + while (ptr < s.size() && s[ptr] != ' ') { + type.push_back(s[ptr]); + ++ptr; + } + if (ptr < s.size()) + ++ptr; // skip ' ' + return make_pair(type, s.substr(ptr)); +} + +/** Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE COOKIEFILE=".../control_auth_cookie"'. + */ +static std::map ParseTorReplyMapping(const std::string &s) +{ + std::map mapping; + size_t ptr=0; + while (ptr < s.size()) { + std::string key, value; + while (ptr < s.size() && s[ptr] != '=') { + key.push_back(s[ptr]); + ++ptr; + } + if (ptr == s.size()) // unexpected end of line + return std::map(); + ++ptr; // skip '=' + if (ptr < s.size() && s[ptr] == '"') { // Quoted string + ++ptr; // skip '=' + bool escape_next = false; + while (ptr < s.size() && (!escape_next && s[ptr] != '"')) { + escape_next = (s[ptr] == '\\'); + value.push_back(s[ptr]); + ++ptr; + } + if (ptr == s.size()) // unexpected end of line + return std::map(); + ++ptr; // skip closing '"' + /* TODO: unescape value - according to the spec this depends on the + * context, some strings use C-LogPrintf style escape codes, some + * don't. So may be better handled at the call site. + */ + } else { // Unquoted value. Note that values can contain '=' at will, just no spaces + while (ptr < s.size() && s[ptr] != ' ') { + value.push_back(s[ptr]); + ++ptr; + } + } + if (ptr < s.size() && s[ptr] == ' ') + ++ptr; // skip ' ' after key=value + mapping[key] = value; + } + return mapping; +} + +/** Read full contents of a file and return them in a std::string. */ +static std::pair ReadBinaryFile(const std::string &filename) +{ + FILE *f = fopen(filename.c_str(), "rb"); + if (f == NULL) + return std::make_pair(false,""); + std::string retval; + char buffer[128]; + size_t n; + while ((n=fread(buffer, 1, sizeof(buffer), f)) > 0) + retval.append(buffer, buffer+n); + fclose(f); + return std::make_pair(true,retval); +} + +/** Write contents of std::string to a file. + * @return true on success. + */ +static bool WriteBinaryFile(const std::string &filename, const std::string &data) +{ + FILE *f = fopen(filename.c_str(), "wb"); + if (f == NULL) + return false; + if (fwrite(data.data(), 1, data.size(), f) != data.size()) + return false; + fclose(f); + return true; +} + +/****** Bitcoin specific TorController implementation ********/ + +/** Controller that connects to Tor control socket, authenticate, then create + * and maintain a ephemeral hidden service. + */ +class TorController +{ +public: + TorController(struct event_base* base, const std::string& target); + ~TorController(); + + /** Get name fo file to store private key in */ + std::string GetPrivateKeyFile(); + + /** Reconnect, after getting disconnected */ + void Reconnect(); +private: + struct event_base* base; + std::string target; + TorControlConnection conn; + std::string private_key; + std::string service_id; + bool reconnect; + struct event *shutdown_poll_ev; + struct event *reconnect_ev; + float reconnect_timeout; + + /** Callback for ADD_ONION result */ + void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for AUTHENTICATE result */ + void auth_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for PROTOCOLINFO result */ + void protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback after succesful connection */ + void connected_cb(TorControlConnection& conn); + /** Callback after connection lost or failed connection attempt */ + void disconnected_cb(TorControlConnection& conn); + + /** Callback for shutdown poll timer */ + static void shutdown_poll_cb(evutil_socket_t fd, short what, void *arg); + /** Callback for reconnect timer */ + static void reconnect_cb(evutil_socket_t fd, short what, void *arg); +}; + +/** Exponential backoff configuration - initial timeout in seconds */ +static const float RECONNECT_TIMEOUT_START = 1.0; +/** Exponential backoff configuration - growth factor */ +static const float RECONNECT_TIMEOUT_EXP = 1.5; + +TorController::TorController(struct event_base* base, const std::string& target): + base(base), + target(target), conn(base), reconnect(true), shutdown_poll_ev(0), reconnect_ev(0), + reconnect_timeout(RECONNECT_TIMEOUT_START) +{ + // Start connection attempts immediately + if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), + boost::bind(&TorController::disconnected_cb, this, _1) )) { + LogPrintf("[tor] Initiating connection to Tor control port %s failed\n", target); + } + // Read service private key if cached + std::pair pkf = ReadBinaryFile(GetPrivateKeyFile()); + if (pkf.first) { + LogPrintf("[tor] Reading cached private key from %s\n", GetPrivateKeyFile()); + private_key = pkf.second; + } + // Periodic timer event to poll for shutdown + // The same 200ms as in bitcoind. This is not the nicest solution, but we cannot exactly use + // boost::interrupt here. + struct timeval time; + time.tv_usec = 200000; + time.tv_sec = 0; + shutdown_poll_ev = event_new(base, -1, EV_PERSIST, shutdown_poll_cb, this); + event_add(shutdown_poll_ev, &time); +} + +TorController::~TorController() +{ + if (shutdown_poll_ev) + event_del(shutdown_poll_ev); + if (reconnect_ev) + event_del(reconnect_ev); +} + +void TorController::add_onion_cb(TorControlConnection& conn, const TorControlReply& reply) +{ + if (reply.code == 250) { + LogPrintf("[tor] ADD_ONION succesful\n"); + BOOST_FOREACH(const std::string &s, reply.lines) { + std::map m = ParseTorReplyMapping(s); + std::map::iterator i; + if ((i = m.find("ServiceID")) != m.end()) + service_id = i->second; + if ((i = m.find("PrivateKey")) != m.end()) + private_key = i->second; + } + + CService service(service_id+".onion", GetListenPort(), false); + LogPrintf("[tor] Got service ID %s, advertizing service %s\n", service_id, service.ToString()); + if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { + LogPrintf("[tor] Cached service private key to %s\n", GetPrivateKeyFile()); + } else { + LogPrintf("[tor] Error writing service private key to %s\n", GetPrivateKeyFile()); + } + AddLocal(service, LOCAL_MANUAL); + // ... onion requested - keep connection open + } else { + LogPrintf("[tor] Add onion failed\n"); + } +} + +void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& reply) +{ + if (reply.code == 250) { + LogPrintf("[tor] Authentication succesful\n"); + // Finally - now create the service + if (private_key.empty()) // No private key, generate one + private_key = "NEW:BEST"; + // Request hidden service, redirect port. + // Note that the 'virtual' port doesn't have to be the same as our internal port, but this is just a convenient + // choice. TODO; refactor the shutdown sequence some day. + conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, GetListenPort(), GetListenPort()), + boost::bind(&TorController::add_onion_cb, this, _1, _2)); + } else { + LogPrintf("[tor] Authentication failed\n"); + } +} + +void TorController::protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply) +{ + if (reply.code == 250) { + std::set methods; + std::string cookiefile; + /* + * 250-AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/home/x/.tor/control_auth_cookie" + * 250-AUTH METHODS=NULL + * 250-AUTH METHODS=HASHEDPASSWORD + */ + BOOST_FOREACH(const std::string &s, reply.lines) { + std::pair l = SplitTorReplyLine(s); + if (l.first == "AUTH") { + std::map m = ParseTorReplyMapping(l.second); + std::map::iterator i; + if ((i = m.find("METHODS")) != m.end()) + boost::split(methods, i->second, boost::is_any_of(",")); + if ((i = m.find("COOKIEFILE")) != m.end()) + cookiefile = i->second; + } else if (l.first == "VERSION") { + std::map m = ParseTorReplyMapping(l.second); + std::map::iterator i; + if ((i = m.find("Tor")) != m.end()) { + LogPrintf("[tor] Connected to Tor version %s\n", i->second); + } + } + } + BOOST_FOREACH(const std::string &s, methods) { + LogPrintf("[tor] Supported authentication method: %s\n", s); + } + // Prefer NULL, otherwise COOKIE. If a password is provided, use HASHEDPASSWORD + // We do not support SAFECOOKIE + /* Authentication: + * cookie: hex-encoded ~/.tor/control_auth_cookie + * password: "password" + */ + if (methods.count("NULL")) { + LogPrintf("[tor] Using NULL authentication\n"); + conn.Command("AUTHENTICATE", boost::bind(&TorController::auth_cb, this, _1, _2)); + } else if (methods.count("COOKIE")) { + // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie + LogPrintf("[tor] Using COOKIE authentication, reading cookie authentication from %s\n", cookiefile); + std::string cookie = ReadBinaryFile(cookiefile).second; + if (!cookie.empty()) { + conn.Command("AUTHENTICATE " + HexStr(cookie), boost::bind(&TorController::auth_cb, this, _1, _2)); + } else { + LogPrintf("[tor] Authentication cookie not found\n"); + } + } else { + /* TODO HASHEDPASSWORD w/ manual auth */ + LogPrintf("[tor] No supported authentication method\n"); + } + } else { + LogPrintf("[tor] Requesting protocol info failed\n"); + } +} + +void TorController::connected_cb(TorControlConnection& conn) +{ + reconnect_timeout = RECONNECT_TIMEOUT_START; + // First send a PROTOCOLINFO command to figure out what authentication is expected + if (!conn.Command("PROTOCOLINFO 1", boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) + LogPrintf("[tor] Error sending initial protocolinfo command\n"); +} + +void TorController::disconnected_cb(TorControlConnection& conn) +{ + if (!reconnect) + return; + LogPrintf("[tor] Disconnected from Tor control port %s, trying to reconnect\n", target); + // Single-shot timer for reconnect. Use exponential backoff. + struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); + reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); + event_add(reconnect_ev, &time); + reconnect_timeout *= RECONNECT_TIMEOUT_EXP; +} + +void TorController::Reconnect() +{ + /* Try to reconnect and reestablish if we get booted - for example, Tor + * may be restarting. + */ + if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), + boost::bind(&TorController::disconnected_cb, this, _1) )) { + LogPrintf("[tor] Re-initiating connection to Tor control port %s failed\n", target); + } +} + +std::string TorController::GetPrivateKeyFile() +{ + return (GetDataDir() / "onion_private_key").string(); +} + +void TorController::shutdown_poll_cb(evutil_socket_t fd, short what, void *arg) +{ + TorController *self = (TorController*)arg; + if (ShutdownRequested()) { + // Shutdown was requested. Stop timers, and request control connection to terminate + LogPrintf("[tor] Thread interrupt\n"); + if (self->shutdown_poll_ev) + event_del(self->shutdown_poll_ev); + self->shutdown_poll_ev = 0; + if (self->reconnect_ev) + event_del(self->reconnect_ev); + self->reconnect_ev = 0; + self->reconnect = false; + self->conn.Disconnect(); + } +} + +void TorController::reconnect_cb(evutil_socket_t fd, short what, void *arg) +{ + TorController *self = (TorController*)arg; + self->Reconnect(); +} + +/****** Thread ********/ + +static void TorControlThread() +{ + struct event_base *base = event_base_new(); + if (!base) { + LogPrintf("[tor] Unable to create event_base_new"); + return; + } + TorController ctrl(base, GetArg("-torcontrol", DEFAULT_TOR_CONTROL)); + + event_base_dispatch(base); + event_base_free(base); +} + +void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler) +{ + threadGroup.create_thread(boost::bind(&TraceThread, "torcontrol", &TorControlThread)); +} + +void StopTorControl() +{ + /* Nothing to do actually. Everything is cleaned up when thread exits */ +} + diff --git a/src/torcontrol.h b/src/torcontrol.h new file mode 100644 index 000000000..fa55f6b03 --- /dev/null +++ b/src/torcontrol.h @@ -0,0 +1,19 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +/** + * Functionality for communicating with Tor. + */ +#ifndef BITCOIN_TORCONTROL_H +#define BITCOIN_TORCONTROL_H + +#include "scheduler.h" + +extern const std::string DEFAULT_TOR_CONTROL; +static const bool DEFAULT_LISTEN_ONION = true; + +void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler); +void StopTorControl(); + +#endif /* BITCOIN_TORCONTROL_H */ From 77e5601e9ee1f957018dd4f5372d0d7adcff0268 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Wed, 26 Aug 2015 21:43:18 -0700 Subject: [PATCH 03/28] Better error message if Tor version too old --- src/torcontrol.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index bb72315c8..40ffbe61b 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -408,8 +408,10 @@ void TorController::add_onion_cb(TorControlConnection& conn, const TorControlRep } AddLocal(service, LOCAL_MANUAL); // ... onion requested - keep connection open + } else if (reply.code == 510) { // 510 Unrecognized command + LogPrintf("[tor] Add onion failed with unrecognized command (You probably need to upgrade Tor)\n"); } else { - LogPrintf("[tor] Add onion failed\n"); + LogPrintf("[tor] Add onion failed; error code %d\n", reply.code); } } From 975dc649af11268473aa9b799877947d45269a0d Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Tue, 8 Sep 2015 17:48:45 +0200 Subject: [PATCH 04/28] torcontrol improvements and fixes - Force AUTHCOOKIE size to be 32 bytes: This provides protection against an attack where a process pretends to be Tor and uses the cookie authentication method to nab arbitrary files such as the wallet - torcontrol logging - fix cookie auth - add HASHEDPASSWORD auth, fix fd leak when fwrite() fails - better error reporting when cookie file is not ok - better init/shutdown flow - stop advertizing service when disconnected from tor control port - COOKIE->SAFECOOKIE auth --- src/init.cpp | 2 + src/net.cpp | 8 ++ src/net.h | 1 + src/torcontrol.cpp | 278 +++++++++++++++++++++++++++++++-------------- src/torcontrol.h | 1 + 5 files changed, 206 insertions(+), 84 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index edbaf0de9..cd53e5813 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -160,6 +160,7 @@ void Interrupt(boost::thread_group& threadGroup) InterruptHTTPRPC(); InterruptRPC(); InterruptREST(); + InterruptTorControl(); threadGroup.interrupt_all(); } @@ -370,6 +371,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-seednode=", _("Connect to a node to retrieve peer addresses, and disconnect")); strUsage += HelpMessageOpt("-timeout=", strprintf(_("Specify connection timeout in milliseconds (minimum: 1, default: %d)"), DEFAULT_CONNECT_TIMEOUT)); strUsage += HelpMessageOpt("-torcontrol=:", strprintf(_("Tor control port to use if onion listening enabled (default: %s)"), DEFAULT_TOR_CONTROL)); + strUsage += HelpMessageOpt("-torpassword=", _("Tor control port password (default: empty)")); #ifdef USE_UPNP #if USE_UPNP strUsage += HelpMessageOpt("-upnp", _("Use UPnP to map the listening port (default: 1 when listening and no -proxy)")); diff --git a/src/net.cpp b/src/net.cpp index e255bfab2..653b7598f 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -259,6 +259,14 @@ bool AddLocal(const CNetAddr &addr, int nScore) return AddLocal(CService(addr, GetListenPort()), nScore); } +bool RemoveLocal(const CService& addr) +{ + LOCK(cs_mapLocalHost); + LogPrintf("RemoveLocal(%s)\n", addr.ToString()); + mapLocalHost.erase(addr); + return true; +} + /** Make a particular network entirely off-limits (no automatic connects to it) */ void SetLimited(enum Network net, bool fLimited) { diff --git a/src/net.h b/src/net.h index 81b1c2e73..c7af1c9d6 100644 --- a/src/net.h +++ b/src/net.h @@ -131,6 +131,7 @@ bool IsLimited(enum Network net); bool IsLimited(const CNetAddr& addr); bool AddLocal(const CService& addr, int nScore = LOCAL_NONE); bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE); +bool RemoveLocal(const CService& addr); bool SeenLocal(const CService& addr); bool IsLocal(const CService& addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = NULL); diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 40ffbe61b..08644f296 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -2,7 +2,7 @@ #include "utilstrencodings.h" #include "net.h" #include "util.h" -#include "init.h" // Just for ShutdownRequested +#include "crypto/hmac_sha256.h" #include #include @@ -16,13 +16,33 @@ #include #include #include +#include #include #include #include #include +#include +/** Default control port */ const std::string DEFAULT_TOR_CONTROL = "127.0.0.1:9051"; +/** Tor cookie size (from control-spec.txt) */ +static const int TOR_COOKIE_SIZE = 32; +/** Size of client/server nonce for SAFECOOKIE */ +static const int TOR_NONCE_SIZE = 32; +/** For computing serverHash in SAFECOOKIE */ +static const std::string TOR_SAFE_SERVERKEY = "Tor safe cookie authentication server-to-controller hash"; +/** For computing clientHash in SAFECOOKIE */ +static const std::string TOR_SAFE_CLIENTKEY = "Tor safe cookie authentication controller-to-server hash"; +/** Exponential backoff configuration - initial timeout in seconds */ +static const float RECONNECT_TIMEOUT_START = 1.0; +/** Exponential backoff configuration - growth factor */ +static const float RECONNECT_TIMEOUT_EXP = 1.5; +/** Maximum length for lines received on TorControlConnection. + * tor-control-spec.txt mentions that there is explicitly no limit defined to line length, + * this is belt-and-suspenders sanity limit to prevent memory exhaustion. + */ +static const int MAX_LINE_LENGTH = 100000; /****** Low-level TorControlConnection ********/ @@ -123,7 +143,7 @@ void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) if (s.size() < 4) // Short line continue; // (-|+| ) - self->message.code = atoi(s.substr(0,3).c_str()); + self->message.code = atoi(s.substr(0,3)); self->message.lines.push_back(s.substr(4)); char ch = s[3]; // '-','+' or ' ' if (ch == ' ') { @@ -138,25 +158,32 @@ void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) self->reply_handlers.front()(*self, self->message); self->reply_handlers.pop_front(); } else { - LogPrintf("[tor] Received unexpected sync reply %i\n", self->message.code); + LogPrint("tor", "tor: Received unexpected sync reply %i\n", self->message.code); } } self->message.Clear(); } } + // Check for size of buffer - protect against memory exhaustion with very long lines + // Do this after evbuffer_readln to make sure all full lines have been + // removed from the buffer. Everything left is an incomplete line. + if (evbuffer_get_length(input) > MAX_LINE_LENGTH) { + LogPrintf("tor: Disconnecting because MAX_LINE_LENGTH exceeded\n"); + self->Disconnect(); + } } void TorControlConnection::eventcb(struct bufferevent *bev, short what, void *ctx) { TorControlConnection *self = (TorControlConnection*)ctx; if (what & BEV_EVENT_CONNECTED) { - LogPrintf("[tor] Succesfully connected!\n"); + LogPrint("tor", "tor: Succesfully connected!\n"); self->connected(*self); } else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { if (what & BEV_EVENT_ERROR) - LogPrintf("[tor] Error connecting to Tor control socket\n"); + LogPrint("tor", "tor: Error connecting to Tor control socket\n"); else - LogPrintf("[tor] End of stream\n"); + LogPrint("tor", "tor: End of stream\n"); self->Disconnect(); self->disconnected(*self); } @@ -171,7 +198,7 @@ bool TorControlConnection::Connect(const std::string &target, const ConnectionCB int connect_to_addrlen = sizeof(connect_to_addr); if (evutil_parse_sockaddr_port(target.c_str(), (struct sockaddr*)&connect_to_addr, &connect_to_addrlen)<0) { - perror("evutil_parse_sockaddr_port\n"); + LogPrintf("tor: Error parsing socket address %s\n", target); return false; } @@ -186,7 +213,7 @@ bool TorControlConnection::Connect(const std::string &target, const ConnectionCB // Finally, connect to target if (bufferevent_socket_connect(b_conn, (struct sockaddr*)&connect_to_addr, connect_to_addrlen) < 0) { - perror("bufferevent_socket_connect"); + LogPrintf("tor: Error connecting to address %s\n", target); return false; } return true; @@ -274,8 +301,14 @@ static std::map ParseTorReplyMapping(const std::string return mapping; } -/** Read full contents of a file and return them in a std::string. */ -static std::pair ReadBinaryFile(const std::string &filename) +/** Read full contents of a file and return them in a std::string. + * Returns a pair . + * If an error occured, status will be false, otherwise status will be true and the data will be returned in string. + * + * @param maxsize Puts a maximum size limit on the file that is read. If the file is larger than this, truncated data + * (with len > maxsize) will be returned. + */ +static std::pair ReadBinaryFile(const std::string &filename, size_t maxsize=std::numeric_limits::max()) { FILE *f = fopen(filename.c_str(), "rb"); if (f == NULL) @@ -283,8 +316,11 @@ static std::pair ReadBinaryFile(const std::string &filename) std::string retval; char buffer[128]; size_t n; - while ((n=fread(buffer, 1, sizeof(buffer), f)) > 0) + while ((n=fread(buffer, 1, sizeof(buffer), f)) > 0) { retval.append(buffer, buffer+n); + if (retval.size() > maxsize) + break; + } fclose(f); return std::make_pair(true,retval); } @@ -297,8 +333,10 @@ static bool WriteBinaryFile(const std::string &filename, const std::string &data FILE *f = fopen(filename.c_str(), "wb"); if (f == NULL) return false; - if (fwrite(data.data(), 1, data.size(), f) != data.size()) + if (fwrite(data.data(), 1, data.size(), f) != data.size()) { + fclose(f); return false; + } fclose(f); return true; } @@ -326,14 +364,20 @@ private: std::string private_key; std::string service_id; bool reconnect; - struct event *shutdown_poll_ev; struct event *reconnect_ev; float reconnect_timeout; + CService service; + /** Cooie for SAFECOOKIE auth */ + std::vector cookie; + /** ClientNonce for SAFECOOKIE auth */ + std::vector clientNonce; /** Callback for ADD_ONION result */ void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback for AUTHENTICATE result */ void auth_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for AUTHCHALLENGE result */ + void authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback for PROTOCOLINFO result */ void protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback after succesful connection */ @@ -341,55 +385,41 @@ private: /** Callback after connection lost or failed connection attempt */ void disconnected_cb(TorControlConnection& conn); - /** Callback for shutdown poll timer */ - static void shutdown_poll_cb(evutil_socket_t fd, short what, void *arg); /** Callback for reconnect timer */ static void reconnect_cb(evutil_socket_t fd, short what, void *arg); }; -/** Exponential backoff configuration - initial timeout in seconds */ -static const float RECONNECT_TIMEOUT_START = 1.0; -/** Exponential backoff configuration - growth factor */ -static const float RECONNECT_TIMEOUT_EXP = 1.5; - TorController::TorController(struct event_base* base, const std::string& target): base(base), - target(target), conn(base), reconnect(true), shutdown_poll_ev(0), reconnect_ev(0), + target(target), conn(base), reconnect(true), reconnect_ev(0), reconnect_timeout(RECONNECT_TIMEOUT_START) { // Start connection attempts immediately if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), boost::bind(&TorController::disconnected_cb, this, _1) )) { - LogPrintf("[tor] Initiating connection to Tor control port %s failed\n", target); + LogPrintf("tor: Initiating connection to Tor control port %s failed\n", target); } // Read service private key if cached std::pair pkf = ReadBinaryFile(GetPrivateKeyFile()); if (pkf.first) { - LogPrintf("[tor] Reading cached private key from %s\n", GetPrivateKeyFile()); + LogPrint("tor", "tor: Reading cached private key from %s\n", GetPrivateKeyFile()); private_key = pkf.second; } - // Periodic timer event to poll for shutdown - // The same 200ms as in bitcoind. This is not the nicest solution, but we cannot exactly use - // boost::interrupt here. - struct timeval time; - time.tv_usec = 200000; - time.tv_sec = 0; - shutdown_poll_ev = event_new(base, -1, EV_PERSIST, shutdown_poll_cb, this); - event_add(shutdown_poll_ev, &time); } TorController::~TorController() { - if (shutdown_poll_ev) - event_del(shutdown_poll_ev); if (reconnect_ev) event_del(reconnect_ev); + if (service.IsValid()) { + RemoveLocal(service); + } } void TorController::add_onion_cb(TorControlConnection& conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrintf("[tor] ADD_ONION succesful\n"); + LogPrint("tor", "tor: ADD_ONION succesful\n"); BOOST_FOREACH(const std::string &s, reply.lines) { std::map m = ParseTorReplyMapping(s); std::map::iterator i; @@ -399,26 +429,26 @@ void TorController::add_onion_cb(TorControlConnection& conn, const TorControlRep private_key = i->second; } - CService service(service_id+".onion", GetListenPort(), false); - LogPrintf("[tor] Got service ID %s, advertizing service %s\n", service_id, service.ToString()); + service = CService(service_id+".onion", GetListenPort(), false); + LogPrintf("tor: Got service ID %s, advertizing service %s\n", service_id, service.ToString()); if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { - LogPrintf("[tor] Cached service private key to %s\n", GetPrivateKeyFile()); + LogPrint("tor", "tor: Cached service private key to %s\n", GetPrivateKeyFile()); } else { - LogPrintf("[tor] Error writing service private key to %s\n", GetPrivateKeyFile()); + LogPrintf("tor: Error writing service private key to %s\n", GetPrivateKeyFile()); } AddLocal(service, LOCAL_MANUAL); // ... onion requested - keep connection open } else if (reply.code == 510) { // 510 Unrecognized command - LogPrintf("[tor] Add onion failed with unrecognized command (You probably need to upgrade Tor)\n"); + LogPrintf("tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)\n"); } else { - LogPrintf("[tor] Add onion failed; error code %d\n", reply.code); + LogPrintf("tor: Add onion failed; error code %d\n", reply.code); } } void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrintf("[tor] Authentication succesful\n"); + LogPrint("tor", "tor: Authentication succesful\n"); // Finally - now create the service if (private_key.empty()) // No private key, generate one private_key = "NEW:BEST"; @@ -428,7 +458,65 @@ void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& r conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, GetListenPort(), GetListenPort()), boost::bind(&TorController::add_onion_cb, this, _1, _2)); } else { - LogPrintf("[tor] Authentication failed\n"); + LogPrintf("tor: Authentication failed\n"); + } +} + +/** Compute Tor SAFECOOKIE response. + * + * ServerHash is computed as: + * HMAC-SHA256("Tor safe cookie authentication server-to-controller hash", + * CookieString | ClientNonce | ServerNonce) + * (with the HMAC key as its first argument) + * + * After a controller sends a successful AUTHCHALLENGE command, the + * next command sent on the connection must be an AUTHENTICATE command, + * and the only authentication string which that AUTHENTICATE command + * will accept is: + * + * HMAC-SHA256("Tor safe cookie authentication controller-to-server hash", + * CookieString | ClientNonce | ServerNonce) + * + */ +static std::vector ComputeResponse(const std::string &key, const std::vector &cookie, const std::vector &clientNonce, const std::vector &serverNonce) +{ + CHMAC_SHA256 computeHash((const uint8_t*)key.data(), key.size()); + std::vector computedHash(CHMAC_SHA256::OUTPUT_SIZE, 0); + computeHash.Write(begin_ptr(cookie), cookie.size()); + computeHash.Write(begin_ptr(clientNonce), clientNonce.size()); + computeHash.Write(begin_ptr(serverNonce), serverNonce.size()); + computeHash.Finalize(begin_ptr(computedHash)); + return computedHash; +} + +void TorController::authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply) +{ + if (reply.code == 250) { + LogPrint("tor", "tor: SAFECOOKIE authentication challenge succesful\n"); + std::pair l = SplitTorReplyLine(reply.lines[0]); + if (l.first == "AUTHCHALLENGE") { + std::map m = ParseTorReplyMapping(l.second); + std::vector serverHash = ParseHex(m["SERVERHASH"]); + std::vector serverNonce = ParseHex(m["SERVERNONCE"]); + LogPrint("tor", "tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", HexStr(serverHash), HexStr(serverNonce)); + if (serverNonce.size() != 32) { + LogPrintf("tor: ServerNonce is not 32 bytes, as required by spec\n"); + return; + } + + std::vector computedServerHash = ComputeResponse(TOR_SAFE_SERVERKEY, cookie, clientNonce, serverNonce); + if (computedServerHash != serverHash) { + LogPrintf("tor: ServerHash %s does not match expected ServerHash %s\n", HexStr(serverHash), HexStr(computedServerHash)); + return; + } + + std::vector computedClientHash = ComputeResponse(TOR_SAFE_CLIENTKEY, cookie, clientNonce, serverNonce); + conn.Command("AUTHENTICATE " + HexStr(computedClientHash), boost::bind(&TorController::auth_cb, this, _1, _2)); + } else { + LogPrintf("tor: Invalid reply to AUTHCHALLENGE\n"); + } + } else { + LogPrintf("tor: SAFECOOKIE authentication challenge failed\n"); } } @@ -455,37 +543,52 @@ void TorController::protocolinfo_cb(TorControlConnection& conn, const TorControl std::map m = ParseTorReplyMapping(l.second); std::map::iterator i; if ((i = m.find("Tor")) != m.end()) { - LogPrintf("[tor] Connected to Tor version %s\n", i->second); + LogPrint("tor", "tor: Connected to Tor version %s\n", i->second); } } } BOOST_FOREACH(const std::string &s, methods) { - LogPrintf("[tor] Supported authentication method: %s\n", s); + LogPrint("tor", "tor: Supported authentication method: %s\n", s); } - // Prefer NULL, otherwise COOKIE. If a password is provided, use HASHEDPASSWORD - // We do not support SAFECOOKIE + // Prefer NULL, otherwise SAFECOOKIE. If a password is provided, use HASHEDPASSWORD /* Authentication: * cookie: hex-encoded ~/.tor/control_auth_cookie * password: "password" */ + std::string torpassword = GetArg("-torpassword", ""); if (methods.count("NULL")) { - LogPrintf("[tor] Using NULL authentication\n"); + LogPrint("tor", "tor: Using NULL authentication\n"); conn.Command("AUTHENTICATE", boost::bind(&TorController::auth_cb, this, _1, _2)); - } else if (methods.count("COOKIE")) { + } else if (methods.count("SAFECOOKIE")) { // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie - LogPrintf("[tor] Using COOKIE authentication, reading cookie authentication from %s\n", cookiefile); - std::string cookie = ReadBinaryFile(cookiefile).second; - if (!cookie.empty()) { - conn.Command("AUTHENTICATE " + HexStr(cookie), boost::bind(&TorController::auth_cb, this, _1, _2)); + LogPrint("tor", "tor: Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); + std::pair status_cookie = ReadBinaryFile(cookiefile, TOR_COOKIE_SIZE); + if (status_cookie.first && status_cookie.second.size() == TOR_COOKIE_SIZE) { + // conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), boost::bind(&TorController::auth_cb, this, _1, _2)); + cookie = std::vector(status_cookie.second.begin(), status_cookie.second.end()); + clientNonce = std::vector(TOR_NONCE_SIZE, 0); + GetRandBytes(&clientNonce[0], TOR_NONCE_SIZE); + conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), boost::bind(&TorController::authchallenge_cb, this, _1, _2)); } else { - LogPrintf("[tor] Authentication cookie not found\n"); + if (status_cookie.first) { + LogPrintf("tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec\n", cookiefile, TOR_COOKIE_SIZE); + } else { + LogPrintf("tor: Authentication cookie %s could not be opened (check permissions)\n", cookiefile); + } + } + } else if (methods.count("HASHEDPASSWORD")) { + if (!torpassword.empty()) { + LogPrint("tor", "tor: Using HASHEDPASSWORD authentication\n"); + boost::replace_all(torpassword, "\"", "\\\""); + conn.Command("AUTHENTICATE \"" + torpassword + "\"", boost::bind(&TorController::auth_cb, this, _1, _2)); + } else { + LogPrintf("tor: Password authentication required, but no password provided with -torpassword\n"); } } else { - /* TODO HASHEDPASSWORD w/ manual auth */ - LogPrintf("[tor] No supported authentication method\n"); + LogPrintf("tor: No supported authentication method\n"); } } else { - LogPrintf("[tor] Requesting protocol info failed\n"); + LogPrintf("tor: Requesting protocol info failed\n"); } } @@ -494,14 +597,18 @@ void TorController::connected_cb(TorControlConnection& conn) reconnect_timeout = RECONNECT_TIMEOUT_START; // First send a PROTOCOLINFO command to figure out what authentication is expected if (!conn.Command("PROTOCOLINFO 1", boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) - LogPrintf("[tor] Error sending initial protocolinfo command\n"); + LogPrintf("tor: Error sending initial protocolinfo command\n"); } void TorController::disconnected_cb(TorControlConnection& conn) { + // Stop advertizing service when disconnected + if (service.IsValid()) + RemoveLocal(service); + service = CService(); if (!reconnect) return; - LogPrintf("[tor] Disconnected from Tor control port %s, trying to reconnect\n", target); + LogPrintf("tor: Disconnected from Tor control port %s, trying to reconnect\n", target); // Single-shot timer for reconnect. Use exponential backoff. struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); @@ -516,7 +623,7 @@ void TorController::Reconnect() */ if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), boost::bind(&TorController::disconnected_cb, this, _1) )) { - LogPrintf("[tor] Re-initiating connection to Tor control port %s failed\n", target); + LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", target); } } @@ -525,23 +632,6 @@ std::string TorController::GetPrivateKeyFile() return (GetDataDir() / "onion_private_key").string(); } -void TorController::shutdown_poll_cb(evutil_socket_t fd, short what, void *arg) -{ - TorController *self = (TorController*)arg; - if (ShutdownRequested()) { - // Shutdown was requested. Stop timers, and request control connection to terminate - LogPrintf("[tor] Thread interrupt\n"); - if (self->shutdown_poll_ev) - event_del(self->shutdown_poll_ev); - self->shutdown_poll_ev = 0; - if (self->reconnect_ev) - event_del(self->reconnect_ev); - self->reconnect_ev = 0; - self->reconnect = false; - self->conn.Disconnect(); - } -} - void TorController::reconnect_cb(evutil_socket_t fd, short what, void *arg) { TorController *self = (TorController*)arg; @@ -549,27 +639,47 @@ void TorController::reconnect_cb(evutil_socket_t fd, short what, void *arg) } /****** Thread ********/ +struct event_base *base; +boost::thread torControlThread; static void TorControlThread() { - struct event_base *base = event_base_new(); - if (!base) { - LogPrintf("[tor] Unable to create event_base_new"); - return; - } TorController ctrl(base, GetArg("-torcontrol", DEFAULT_TOR_CONTROL)); event_base_dispatch(base); - event_base_free(base); } void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler) { - threadGroup.create_thread(boost::bind(&TraceThread, "torcontrol", &TorControlThread)); + assert(!base); +#ifdef WIN32 + evthread_use_windows_threads(); +#else + evthread_use_pthreads(); +#endif + base = event_base_new(); + if (!base) { + LogPrintf("tor: Unable to create event_base\n"); + return; + } + + torControlThread = boost::thread(boost::bind(&TraceThread, "torcontrol", &TorControlThread)); +} + +void InterruptTorControl() +{ + if (base) { + LogPrintf("tor: Thread interrupt\n"); + event_base_loopbreak(base); + } } void StopTorControl() { - /* Nothing to do actually. Everything is cleaned up when thread exits */ + if (base) { + torControlThread.join(); + event_base_free(base); + base = 0; + } } diff --git a/src/torcontrol.h b/src/torcontrol.h index fa55f6b03..72dc82c5b 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -14,6 +14,7 @@ extern const std::string DEFAULT_TOR_CONTROL; static const bool DEFAULT_LISTEN_ONION = true; void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler); +void InterruptTorControl(); void StopTorControl(); #endif /* BITCOIN_TORCONTROL_H */ From 2298877f78027b606fe05bb4745d123b67c1a841 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Wed, 11 Nov 2015 15:08:38 +0100 Subject: [PATCH 05/28] doc: update docs for Tor listening - add new data directory files for 0.12 to doc/files.md - mention torcontrol in doc/tor.md --- doc/files.md | 2 ++ doc/tor.md | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/doc/files.md b/doc/files.md index 260d36ef3..2d3787912 100644 --- a/doc/files.md +++ b/doc/files.md @@ -10,3 +10,5 @@ * fee_estimates.dat: stores statistics used to estimate minimum transaction fees and priorities required for confirmation * peers.dat: peer IP address database (custom format) * wallet.dat: personal wallet (BDB) with keys and transactions +* .cookie: session RPC authentication cookie (written at start when cookie authentication is used, deleted on shutdown): since 0.12.0 +* onion_private_key: cached Tor hidden service private key for `-listenonion`: since 0.12.0 diff --git a/doc/tor.md b/doc/tor.md index 61549b631..384bae387 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -86,7 +86,25 @@ for normal IPv4/IPv6 communication, use: ./zcashd -onion=127.0.0.1:9050 -externalip=zctestseie6wxgio.onion -discover -3. Connect to a Zcash hidden server +3. Automatically listen on Tor +-------------------------------- + +Starting with Tor version 0.2.7.1 it is possible, through Tor's control socket +API, to create and destroy 'ephemeral' hidden services programmatically. +Zcash has been updated to make use of this. + +This means that if Tor is running (and proper authorization is available), +Zcash automatically creates a hidden service to listen on, without +manual configuration. This will positively affect the number of available +.onion nodes. + +This new feature is enabled by default if Zcash is listening, and +a connection to Tor can be made. It can be configured with the `-listenonion`, +`-torcontrol` and `-torpassword` settings. To show verbose debugging +information, pass `-debug=tor`. + + +4. Connect to a Zcash hidden server ----------------------------------- To test your set-up, you might want to try connecting via Tor on a different computer to just a From 7f9e7a9872758aa3c240312e63baf45d767b58c9 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Sun, 6 Sep 2015 17:54:41 +0200 Subject: [PATCH 06/28] [doc] [tor] Clarify when to use bind c.f. #6585 --- doc/tor.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/tor.md b/doc/tor.md index 384bae387..2aa352d46 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -72,9 +72,14 @@ In a typical situation, where you're only reachable via Tor, this should suffice ./zcashd -proxy=127.0.0.1:9050 -externalip=zctestseie6wxgio.onion -listen -(obviously, replace the Onion address with your own). If you don't care too much -about hiding your node, and want to be reachable on IPv4 as well, additionally -specify: +(obviously, replace the Onion address with your own). It should be noted that you still +listen on all devices and another node could establish a clearnet connection, when knowing +your address. To mitigate this, additionally bind the address of your Tor proxy: + + ./bitcoind ... -bind=127.0.0.1 + +If you don't care too much about hiding your node, and want to be reachable on IPv4 +as well, use `discover` instead: ./zcashd ... -discover From 2b30758b2fb82cb0b32885c0bd2cf8bf11e90d57 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Tue, 24 Nov 2015 10:27:38 -0500 Subject: [PATCH 07/28] Connect to Tor hidden services by default Adds 127.0.0.1:9050 for the .onion proxy if we can succesfully connect to the control port. Natural followup to creating hidden services automatically. --- doc/tor.md | 5 +++-- src/torcontrol.cpp | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/tor.md b/doc/tor.md index 2aa352d46..6c912e2a7 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -100,8 +100,9 @@ Zcash has been updated to make use of this. This means that if Tor is running (and proper authorization is available), Zcash automatically creates a hidden service to listen on, without -manual configuration. This will positively affect the number of available -.onion nodes. +manual configuration. Zcash will also use Tor automatically to connect +to other .onion nodes if the control socket can be successfully opened. This +will positively affect the number of available .onion nodes and their usage. This new feature is enabled by default if Zcash is listening, and a connection to Tor can be made. It can be configured with the `-listenonion`, diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 08644f296..31a291720 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -449,6 +449,15 @@ void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& r { if (reply.code == 250) { LogPrint("tor", "tor: Authentication succesful\n"); + + // Now that we know Tor is running setup the proxy for onion addresses + // if -onion isn't set to something else. + if (GetArg("-onion", "") == "") { + proxyType addrOnion = proxyType(CService("127.0.0.1", 9050), true); + SetProxy(NET_TOR, addrOnion); + SetReachable(NET_TOR); + } + // Finally - now create the service if (private_key.empty()) // No private key, generate one private_key = "NEW:BEST"; From 65fd8eb134e6581b924521832c3b3eb1bdefa7b7 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 17 Nov 2015 12:10:28 +1100 Subject: [PATCH 08/28] torcontrol: only output disconnect if -debug=tor --- src/init.cpp | 2 +- src/torcontrol.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index cd53e5813..e7071e1f7 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -427,7 +427,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", 0)); } string debugCategories = "addrman, alert, bench, coindb, db, estimatefee, http, libevent, lock, mempool, net, partitioncheck, pow, proxy, prune, " - "rand, reindex, rpc, selectcoins, zmq, zrpc, zrpcunsafe (implies zrpc)"; // Don't translate these + "rand, reindex, rpc, selectcoins, tor, zmq, zrpc, zrpcunsafe (implies zrpc)"; // Don't translate these strUsage += HelpMessageOpt("-debug=", strprintf(_("Output debugging information (default: %u, supplying is optional)"), 0) + ". " + _("If is not supplied or if = 1, output all debugging information.") + " " + _(" can be:") + " " + debugCategories + "."); strUsage += HelpMessageOpt("-experimentalfeatures", _("Enable use of experimental features")); diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 31a291720..8eccc81e3 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -617,7 +617,9 @@ void TorController::disconnected_cb(TorControlConnection& conn) service = CService(); if (!reconnect) return; - LogPrintf("tor: Disconnected from Tor control port %s, trying to reconnect\n", target); + + LogPrint("tor", "tor: Disconnected from Tor control port %s, trying to reconnect\n", target); + // Single-shot timer for reconnect. Use exponential backoff. struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); From f3e1770cfb4cd5488639608b3571a7ad09736cc4 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Fri, 4 Dec 2015 13:24:12 +0100 Subject: [PATCH 09/28] tests: Disable Tor interaction This is unnecessary during the current tests (any test for Tor interaction can explicitly enable it) and interferes with the proxy test. --- qa/rpc-tests/test_framework/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index 5d03403e2..f5ef9dec6 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -83,6 +83,7 @@ def initialize_datadir(dirname, n): f.write("rpcpassword=rt\n"); f.write("port="+str(p2p_port(n))+"\n"); f.write("rpcport="+str(rpc_port(n))+"\n"); + f.write("listenonion=0\n"); return datadir def initialize_chain(test_dir): From 9b46a35b8d4ba21818fb73aaf08e140796fa2797 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Tue, 15 Dec 2015 17:03:08 +0100 Subject: [PATCH 10/28] torcontrol debug: Change to a blanket message that covers both cases --- src/torcontrol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 8eccc81e3..4ebcb9b66 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -618,7 +618,7 @@ void TorController::disconnected_cb(TorControlConnection& conn) if (!reconnect) return; - LogPrint("tor", "tor: Disconnected from Tor control port %s, trying to reconnect\n", target); + LogPrint("tor", "tor: Not connected to Tor control port %s, trying to reconnect\n", target); // Single-shot timer for reconnect. Use exponential backoff. struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); From e10e21247036121ccd13888d848571c569bb6387 Mon Sep 17 00:00:00 2001 From: calebogden Date: Fri, 8 Jan 2016 13:31:42 -0800 Subject: [PATCH 11/28] Fixing typos on security-check.py and torcontrol.cpp --- src/torcontrol.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 4ebcb9b66..9a783b970 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -79,7 +79,7 @@ public: /** * Connect to a Tor control port. * target is address of the form host:port. - * connected is the handler that is called when connection is succesfully established. + * connected is the handler that is called when connection is successfully established. * disconnected is a handler that is called when the connection is broken. * Return true on success. */ @@ -177,7 +177,7 @@ void TorControlConnection::eventcb(struct bufferevent *bev, short what, void *ct { TorControlConnection *self = (TorControlConnection*)ctx; if (what & BEV_EVENT_CONNECTED) { - LogPrint("tor", "tor: Succesfully connected!\n"); + LogPrint("tor", "tor: Successfully connected!\n"); self->connected(*self); } else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { if (what & BEV_EVENT_ERROR) @@ -380,7 +380,7 @@ private: void authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback for PROTOCOLINFO result */ void protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback after succesful connection */ + /** Callback after successful connection */ void connected_cb(TorControlConnection& conn); /** Callback after connection lost or failed connection attempt */ void disconnected_cb(TorControlConnection& conn); @@ -419,7 +419,7 @@ TorController::~TorController() void TorController::add_onion_cb(TorControlConnection& conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint("tor", "tor: ADD_ONION succesful\n"); + LogPrint("tor", "tor: ADD_ONION successful\n"); BOOST_FOREACH(const std::string &s, reply.lines) { std::map m = ParseTorReplyMapping(s); std::map::iterator i; @@ -448,7 +448,7 @@ void TorController::add_onion_cb(TorControlConnection& conn, const TorControlRep void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint("tor", "tor: Authentication succesful\n"); + LogPrint("tor", "tor: Authentication successful\n"); // Now that we know Tor is running setup the proxy for onion addresses // if -onion isn't set to something else. @@ -501,7 +501,7 @@ static std::vector ComputeResponse(const std::string &key, const std::v void TorController::authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint("tor", "tor: SAFECOOKIE authentication challenge succesful\n"); + LogPrint("tor", "tor: SAFECOOKIE authentication challenge successful\n"); std::pair l = SplitTorReplyLine(reply.lines[0]); if (l.first == "AUTHCHALLENGE") { std::map m = ParseTorReplyMapping(l.second); From 35db253fb64c62295256c8241af77a6081d653ac Mon Sep 17 00:00:00 2001 From: Gregory Maxwell Date: Thu, 28 Jan 2016 22:44:14 +0000 Subject: [PATCH 12/28] Do not absolutely protect local peers from eviction. With automatic tor HS support in place we should probably not be providing absolute protection for local peers, since HS inbound could be used to attack pretty easily. Instead, this counts on the latency metric inside AttemptToEvictConnection to privilege actually local peers. --- src/net.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 653b7598f..ad20bb223 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -821,8 +821,6 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) { continue; if (node->fDisconnect) continue; - if (node->addr.IsLocal()) - continue; vEvictionCandidates.push_back(CNodeRef(node)); } } From f28a87b4bab9e9b58e01b16df19b84dc71287422 Mon Sep 17 00:00:00 2001 From: Gregory Maxwell Date: Mon, 23 Nov 2015 03:48:54 +0000 Subject: [PATCH 13/28] Decide eviction group ties based on time. This corrects a bug the case of tying group size where the code may fail to select the group with the newest member. Since newest time is the final selection criteria, failing to break ties on it on the step before can undermine the final selection. Tied netgroups are very common. --- src/net.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index ad20bb223..e9f544d87 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -851,15 +851,20 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) { if (vEvictionCandidates.empty()) return false; - // Identify the network group with the most connections + // Identify the network group with the most connections and youngest member. + // (vEvictionCandidates is already sorted by reverse connect time) std::vector naMostConnections; unsigned int nMostConnections = 0; + int64_t nMostConnectionsTime = 0; std::map, std::vector > mapAddrCounts; BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) { mapAddrCounts[node->addr.GetGroup()].push_back(node); + int64_t grouptime = mapAddrCounts[node->addr.GetGroup()][0]->nTimeConnected; + size_t groupsize = mapAddrCounts[node->addr.GetGroup()].size(); - if (mapAddrCounts[node->addr.GetGroup()].size() > nMostConnections) { - nMostConnections = mapAddrCounts[node->addr.GetGroup()].size(); + if (groupsize > nMostConnections || (groupsize == nMostConnections && grouptime > nMostConnectionsTime)) { + nMostConnections = groupsize; + nMostConnectionsTime = grouptime; naMostConnections = node->addr.GetGroup(); } } @@ -867,14 +872,13 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) { // Reduce to the network group with the most connections vEvictionCandidates = mapAddrCounts[naMostConnections]; - // Do not disconnect peers if there is only 1 connection from their network group + // Do not disconnect peers if there is only one unprotected connection from their network group. if (vEvictionCandidates.size() <= 1) // unless we prefer the new connection (for whitelisted peers) if (!fPreferNewConnection) return false; - // Disconnect the most recent connection from the network group with the most connections - std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected); + // Disconnect from the network group with the most connections vEvictionCandidates[0]->fDisconnect = true; return true; From a05be280e7e44eb4c68b2bc72f3cfa342a35e5b1 Mon Sep 17 00:00:00 2001 From: Patrick Strateman Date: Wed, 17 Feb 2016 22:44:32 -0800 Subject: [PATCH 14/28] Remove vfReachable and modify IsReachable to only use vfLimited. We do not know that a class of Network is reachable, only that it is not. --- src/init.cpp | 7 ++++--- src/net.cpp | 12 +----------- src/net.h | 1 - src/torcontrol.cpp | 2 +- 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index e7071e1f7..ec3553c46 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1157,6 +1157,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // -proxy sets a proxy for all outgoing network traffic // -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default std::string proxyArg = GetArg("-proxy", ""); + SetLimited(NET_TOR); if (proxyArg != "" && proxyArg != "0") { proxyType addrProxy = proxyType(CService(proxyArg, 9050), proxyRandomize); if (!addrProxy.IsValid()) @@ -1166,7 +1167,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) SetProxy(NET_IPV6, addrProxy); SetProxy(NET_TOR, addrProxy); SetNameProxy(addrProxy); - SetReachable(NET_TOR); // by default, -proxy sets onion as reachable, unless -noonion later + SetLimited(NET_TOR, false); // by default, -proxy sets onion as reachable, unless -noonion later } // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses @@ -1175,13 +1176,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) std::string onionArg = GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 - SetReachable(NET_TOR, false); // set onions as unreachable + SetLimited(NET_TOR); // set onions as unreachable } else { proxyType addrOnion = proxyType(CService(onionArg, 9050), proxyRandomize); if (!addrOnion.IsValid()) return InitError(strprintf(_("Invalid -onion address: '%s'"), onionArg)); SetProxy(NET_TOR, addrOnion); - SetReachable(NET_TOR); + SetLimited(NET_TOR, false); } } diff --git a/src/net.cpp b/src/net.cpp index e9f544d87..de624c72a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -72,7 +72,6 @@ bool fListen = true; uint64_t nLocalServices = NODE_NETWORK; CCriticalSection cs_mapLocalHost; map mapLocalHost; -static bool vfReachable[NET_MAX] = {}; static bool vfLimited[NET_MAX] = {}; static CNode* pnodeLocalHost = NULL; uint64_t nLocalHostNonce = 0; @@ -218,14 +217,6 @@ void AdvertizeLocal(CNode *pnode) } } -void SetReachable(enum Network net, bool fFlag) -{ - LOCK(cs_mapLocalHost); - vfReachable[net] = fFlag; - if (net == NET_IPV6 && fFlag) - vfReachable[NET_IPV4] = true; -} - // learn a new local address bool AddLocal(const CService& addr, int nScore) { @@ -248,7 +239,6 @@ bool AddLocal(const CService& addr, int nScore) info.nScore = nScore + (fAlready ? 1 : 0); info.nPort = addr.GetPort(); } - SetReachable(addr.GetNetwork()); } return true; @@ -311,7 +301,7 @@ bool IsLocal(const CService& addr) bool IsReachable(enum Network net) { LOCK(cs_mapLocalHost); - return vfReachable[net] && !vfLimited[net]; + return !vfLimited[net]; } /** check whether a given address is in a network we can probably connect to */ diff --git a/src/net.h b/src/net.h index c7af1c9d6..f7ebf11f6 100644 --- a/src/net.h +++ b/src/net.h @@ -137,7 +137,6 @@ bool IsLocal(const CService& addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = NULL); bool IsReachable(enum Network net); bool IsReachable(const CNetAddr &addr); -void SetReachable(enum Network net, bool fFlag = true); CAddress GetLocalAddress(const CNetAddr *paddrPeer = NULL); diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 9a783b970..8a5b91a32 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -455,7 +455,7 @@ void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& r if (GetArg("-onion", "") == "") { proxyType addrOnion = proxyType(CService("127.0.0.1", 9050), true); SetProxy(NET_TOR, addrOnion); - SetReachable(NET_TOR); + SetLimited(NET_TOR, false); } // Finally - now create the service From 3d7cddcaa2ea07297cabe041fba5125176dfb6ca Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Thu, 3 Mar 2016 13:28:07 +0100 Subject: [PATCH 15/28] Fix memleak in TorController [rework] It looks like, TorController::disconnected_cb(TorControlConnection& conn) gets called multiple times which results in multiple event_new(). Avoid this by creating the event only once in the constructore, and deleting it only once in the destructor (thanks to Cory Fields for the idea). Replaces the fix by Jonas Schnelli in #7610, see discussion there. --- src/torcontrol.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 8a5b91a32..f2d41d108 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -394,6 +394,9 @@ TorController::TorController(struct event_base* base, const std::string& target) target(target), conn(base), reconnect(true), reconnect_ev(0), reconnect_timeout(RECONNECT_TIMEOUT_START) { + reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); + if (!reconnect_ev) + LogPrintf("tor: Failed to create event for reconnection: out of memory?\n"); // Start connection attempts immediately if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), boost::bind(&TorController::disconnected_cb, this, _1) )) { @@ -409,8 +412,10 @@ TorController::TorController(struct event_base* base, const std::string& target) TorController::~TorController() { - if (reconnect_ev) - event_del(reconnect_ev); + if (reconnect_ev) { + event_free(reconnect_ev); + reconnect_ev = 0; + } if (service.IsValid()) { RemoveLocal(service); } @@ -622,8 +627,8 @@ void TorController::disconnected_cb(TorControlConnection& conn) // Single-shot timer for reconnect. Use exponential backoff. struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); - reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); - event_add(reconnect_ev, &time); + if (reconnect_ev) + event_add(reconnect_ev, &time); reconnect_timeout *= RECONNECT_TIMEOUT_EXP; } From 1a41e3f660b14b21ffa479b5b70fe790f1713cb3 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Mon, 14 Mar 2016 16:07:42 +0100 Subject: [PATCH 16/28] Fix torcontrol.cpp unused private field warning --- src/torcontrol.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index f2d41d108..8273729ca 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -389,8 +389,8 @@ private: static void reconnect_cb(evutil_socket_t fd, short what, void *arg); }; -TorController::TorController(struct event_base* base, const std::string& target): - base(base), +TorController::TorController(struct event_base* baseIn, const std::string& target): + base(baseIn), target(target), conn(base), reconnect(true), reconnect_ev(0), reconnect_timeout(RECONNECT_TIMEOUT_START) { From ec8828aff3e0edc378e6c25011b407185a3c47e4 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 29 Mar 2016 15:16:16 -0300 Subject: [PATCH 17/28] [doc] Update port in tor.md Tor Browser Bundle spawns the Tor process and listens on port 9150, it doesn't randomly pick a port. [ci skip] (cherry picked from commit 1b63cf98347b2a62915425576930f55c2126c2ff) --- doc/tor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tor.md b/doc/tor.md index 6c912e2a7..4b7485a6e 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -5,7 +5,7 @@ TOR SUPPORT IN ZCASH It is possible to run Zcash as a Tor hidden service, and connect to such services. -The following directions assume you have a Tor proxy running on port 9050. Many distributions default to having a SOCKS proxy listening on port 9050, but others may not. In particular, the Tor Browser Bundle defaults to listening on a random port. See [Tor Project FAQ:TBBSocksPort](https://www.torproject.org/docs/faq.html.en#TBBSocksPort) for how to properly +The following directions assume you have a Tor proxy running on port 9050. Many distributions default to having a SOCKS proxy listening on port 9050, but others may not. In particular, the Tor Browser Bundle defaults to listening on port 9150. See [Tor Project FAQ:TBBSocksPort](https://www.torproject.org/docs/faq.html.en#TBBSocksPort) for how to properly configure Tor. From ca5e2295149a92ee968245220076d362cec78709 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Thu, 17 Mar 2016 12:49:16 +0100 Subject: [PATCH 18/28] tor: Change auth order to only use HASHEDPASSWORD if -torpassword Change authentication order to make it more clear (see #7700). - If the `-torpassword` option is provided, force use of `HASHEDPASSWORD` auth. - Give error message if `-torpassword` provided, but `HASHEDPASSWORD` auth is not available. - Give error message if only `HASHEDPASSWORD` available, but `-torpassword` not given. --- src/torcontrol.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 8273729ca..d0061592f 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -570,7 +570,15 @@ void TorController::protocolinfo_cb(TorControlConnection& conn, const TorControl * password: "password" */ std::string torpassword = GetArg("-torpassword", ""); - if (methods.count("NULL")) { + if (!torpassword.empty()) { + if (methods.count("HASHEDPASSWORD")) { + LogPrint("tor", "tor: Using HASHEDPASSWORD authentication\n"); + boost::replace_all(torpassword, "\"", "\\\""); + conn.Command("AUTHENTICATE \"" + torpassword + "\"", boost::bind(&TorController::auth_cb, this, _1, _2)); + } else { + LogPrintf("tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available\n"); + } + } else if (methods.count("NULL")) { LogPrint("tor", "tor: Using NULL authentication\n"); conn.Command("AUTHENTICATE", boost::bind(&TorController::auth_cb, this, _1, _2)); } else if (methods.count("SAFECOOKIE")) { @@ -591,13 +599,7 @@ void TorController::protocolinfo_cb(TorControlConnection& conn, const TorControl } } } else if (methods.count("HASHEDPASSWORD")) { - if (!torpassword.empty()) { - LogPrint("tor", "tor: Using HASHEDPASSWORD authentication\n"); - boost::replace_all(torpassword, "\"", "\\\""); - conn.Command("AUTHENTICATE \"" + torpassword + "\"", boost::bind(&TorController::auth_cb, this, _1, _2)); - } else { - LogPrintf("tor: Password authentication required, but no password provided with -torpassword\n"); - } + LogPrintf("tor: The only supported authentication mechanism left is password, but no password provided with -torpassword\n"); } else { LogPrintf("tor: No supported authentication method\n"); } From 44040731854c8f69d072f5995e09fb6ec1e0a468 Mon Sep 17 00:00:00 2001 From: Nathaniel Mahieu Date: Tue, 14 Jun 2016 17:49:09 -0500 Subject: [PATCH 19/28] Clarify documentation for running a tor node Previous wording suggested that no additional setup was required for a tor hidden service to be created. --- doc/tor.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/doc/tor.md b/doc/tor.md index 4b7485a6e..d083f4613 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -98,17 +98,28 @@ Starting with Tor version 0.2.7.1 it is possible, through Tor's control socket API, to create and destroy 'ephemeral' hidden services programmatically. Zcash has been updated to make use of this. -This means that if Tor is running (and proper authorization is available), -Zcash automatically creates a hidden service to listen on, without -manual configuration. Zcash will also use Tor automatically to connect -to other .onion nodes if the control socket can be successfully opened. This -will positively affect the number of available .onion nodes and their usage. +This means that if Tor is running (and proper authentication has been configured), +Zcash automatically creates a hidden service to listen on. Zcash will also use Tor +automatically to connect to other .onion nodes if the control socket can be +successfully opened. This will positively affect the number of available .onion +nodes and their usage. This new feature is enabled by default if Zcash is listening, and a connection to Tor can be made. It can be configured with the `-listenonion`, `-torcontrol` and `-torpassword` settings. To show verbose debugging information, pass `-debug=tor`. +Connecting to Tor's control socket API requires one of two authentication methods to be +configured. For cookie authentication the user running zcashd must have write access +to the `CookieAuthFile` specified in Tor configuration. In some cases this is +preconfigured and the creation of a hidden service is automatic. If permission problems +are seen with `-debug=tor` they can be resolved by adding both the user running tor and +the user running zcashd to the same group and setting permissions appropriately. On +Debian-based systems the user running zcashd can be added to the debian-tor group, +which has the appropriate permissions. An alternative authentication method is the use +of the `-torpassword` flag and a `hash-password` which can be enabled and specified in +Tor configuration. + 4. Connect to a Zcash hidden server ----------------------------------- From 5aa2365eddfe7ca5f19a6838d169c8a422a532c0 Mon Sep 17 00:00:00 2001 From: unsystemizer Date: Mon, 24 Oct 2016 15:49:46 +0800 Subject: [PATCH 20/28] Clarify `listenonion` > This new feature is enabled by default if Bitcoin Core is listening, and a connection to Tor can be made. It can be configured with the -listenonion, -torcontrol and -torpassword settings. To show verbose debugging information, pass -debug=tor. But it is correct to say that the feature is enabled *regardless* of whether a connection to Tor can be made. I propose to clarify that so that users can eliminate these in their logs (when `listen=1` and no Tor). And I think it's okay to clarify about the `listen` option, because on several occasions when I read this before I always assumed `listening` meant `server=1` which cost me a lot of time in troubleshooting. ``` 2016-10-24 06:19:22.551029 tor: Error connecting to Tor control socket 2016-10-24 06:19:22.551700 tor: Not connected to Tor control port 127.0.0.1:9051, trying to reconnect ``` 0.12.1 --- doc/tor.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/tor.md b/doc/tor.md index d083f4613..ce717515a 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -104,10 +104,10 @@ automatically to connect to other .onion nodes if the control socket can be successfully opened. This will positively affect the number of available .onion nodes and their usage. -This new feature is enabled by default if Zcash is listening, and -a connection to Tor can be made. It can be configured with the `-listenonion`, -`-torcontrol` and `-torpassword` settings. To show verbose debugging -information, pass `-debug=tor`. +This new feature is enabled by default if Zcash is listening (`-listen`), and +requires a Tor connection to work. It can be explicitly disabled with `-listenonion=0` +and, if not disabled, configured using the `-torcontrol` and `-torpassword` settings. +To show verbose debugging information, pass `-debug=tor`. Connecting to Tor's control socket API requires one of two authentication methods to be configured. For cookie authentication the user running zcashd must have write access From 9e5c9d0a04ca6b121867f7e67bebea2187f1ed27 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Mon, 28 Nov 2016 17:13:51 +0100 Subject: [PATCH 21/28] torcontrol: Explicitly request RSA1024 private key When generating a new service key, explicitly request a RSA1024 one. The bitcoin P2P protocol has no support for the longer hidden service names that will come with ed25519 keys, until it does, we depend on the old hidden service type so make this explicit. See #9214. --- src/torcontrol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index d0061592f..a0f13d3c2 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -465,7 +465,7 @@ void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& r // Finally - now create the service if (private_key.empty()) // No private key, generate one - private_key = "NEW:BEST"; + private_key = "NEW:RSA1024"; // Explicitly request RSA1024 - see issue #9214 // Request hidden service, redirect port. // Note that the 'virtual' port doesn't have to be the same as our internal port, but this is just a convenient // choice. TODO; refactor the shutdown sequence some day. From f0e9019223d297e8336153b0e8c300156b37c127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Jan=C3=ADk?= Date: Fri, 3 Mar 2017 16:20:33 +0100 Subject: [PATCH 22/28] Prevent -Wshadow warnings with gcc versions 4.8.5, 5.3.1 and 6.2.1. Zcash: partial (ignoring src/test/script_tests.cpp due to merge conflicts) --- src/streams.h | 4 ++-- src/torcontrol.cpp | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/streams.h b/src/streams.h index 9b678b3c0..81b58fd05 100644 --- a/src/streams.h +++ b/src/streams.h @@ -295,8 +295,8 @@ public: return (*this); } - void GetAndClear(CSerializeData &data) { - data.insert(data.end(), begin(), end()); + void GetAndClear(CSerializeData &d) { + d.insert(d.end(), begin(), end()); clear(); } }; diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index a0f13d3c2..9141419c8 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -657,26 +657,26 @@ void TorController::reconnect_cb(evutil_socket_t fd, short what, void *arg) } /****** Thread ********/ -struct event_base *base; +struct event_base *gBase; boost::thread torControlThread; static void TorControlThread() { - TorController ctrl(base, GetArg("-torcontrol", DEFAULT_TOR_CONTROL)); + TorController ctrl(gBase, GetArg("-torcontrol", DEFAULT_TOR_CONTROL)); - event_base_dispatch(base); + event_base_dispatch(gBase); } void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler) { - assert(!base); + assert(!gBase); #ifdef WIN32 evthread_use_windows_threads(); #else evthread_use_pthreads(); #endif - base = event_base_new(); - if (!base) { + gBase = event_base_new(); + if (!gBase) { LogPrintf("tor: Unable to create event_base\n"); return; } @@ -686,18 +686,18 @@ void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler) void InterruptTorControl() { - if (base) { + if (gBase) { LogPrintf("tor: Thread interrupt\n"); - event_base_loopbreak(base); + event_base_loopbreak(gBase); } } void StopTorControl() { - if (base) { + if (gBase) { torControlThread.join(); - event_base_free(base); - base = 0; + event_base_free(gBase); + gBase = 0; } } From 4b5ba449bb7c78e961ea1ca981fc044e9e12426a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Jan=C3=ADk?= Date: Wed, 8 Mar 2017 22:13:15 +0100 Subject: [PATCH 23/28] Make some global variables less-global (static) --- src/torcontrol.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 9141419c8..b3229bab7 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -657,8 +657,8 @@ void TorController::reconnect_cb(evutil_socket_t fd, short what, void *arg) } /****** Thread ********/ -struct event_base *gBase; -boost::thread torControlThread; +static struct event_base *gBase; +static boost::thread torControlThread; static void TorControlThread() { From 8966598033417093f790b298e7805994066be2c7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 25 Mar 2017 20:13:18 +1300 Subject: [PATCH 24/28] torcontrol: Improve comments --- src/torcontrol.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index b3229bab7..97efe2bb5 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -244,6 +244,8 @@ bool TorControlConnection::Command(const std::string &cmd, const ReplyHandlerCB& /* Split reply line in the form 'AUTH METHODS=...' into a type * 'AUTH' and arguments 'METHODS=...'. + * Grammar is implicitly defined in https://spec.torproject.org/control-spec by + * the server reply formats for PROTOCOLINFO (S3.21) and AUTHCHALLENGE (S3.24). */ static std::pair SplitTorReplyLine(const std::string &s) { @@ -259,6 +261,9 @@ static std::pair SplitTorReplyLine(const std::string &s } /** Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE COOKIEFILE=".../control_auth_cookie"'. + * Grammar is implicitly defined in https://spec.torproject.org/control-spec by + * the server reply formats for PROTOCOLINFO (S3.21), AUTHCHALLENGE (S3.24), + * and ADD_ONION (S3.27). See also sections 2.1 and 2.3. */ static std::map ParseTorReplyMapping(const std::string &s) { @@ -274,7 +279,7 @@ static std::map ParseTorReplyMapping(const std::string return std::map(); ++ptr; // skip '=' if (ptr < s.size() && s[ptr] == '"') { // Quoted string - ++ptr; // skip '=' + ++ptr; // skip opening '"' bool escape_next = false; while (ptr < s.size() && (!escape_next && s[ptr] != '"')) { escape_next = (s[ptr] == '\\'); From 6dbd95afa10ef2ab1f2a14e56e48e190e195157a Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 25 Mar 2017 20:17:37 +1300 Subject: [PATCH 25/28] torcontrol: Add unit tests for Tor reply parsers --- src/Makefile.test.include | 3 +- src/test/torcontrol_tests.cpp | 168 ++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/test/torcontrol_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 7de8c392f..c97ea692d 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -84,6 +84,7 @@ BITCOIN_TESTS =\ test/test_bitcoin.cpp \ test/test_bitcoin.h \ test/timedata_tests.cpp \ + test/torcontrol_tests.cpp \ test/transaction_tests.cpp \ test/uint256_tests.cpp \ test/univalue_tests.cpp \ @@ -100,7 +101,7 @@ endif test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = -fopenmp $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS) test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ - $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) + $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) endif diff --git a/src/test/torcontrol_tests.cpp b/src/test/torcontrol_tests.cpp new file mode 100644 index 000000000..68516599d --- /dev/null +++ b/src/test/torcontrol_tests.cpp @@ -0,0 +1,168 @@ +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// +#include "test/test_bitcoin.h" +#include "torcontrol.cpp" + +#include + + +BOOST_FIXTURE_TEST_SUITE(torcontrol_tests, BasicTestingSetup) + +void CheckSplitTorReplyLine(std::string input, std::string command, std::string args) +{ + BOOST_TEST_MESSAGE(std::string("CheckSplitTorReplyLine(") + input + ")"); + auto ret = SplitTorReplyLine(input); + BOOST_CHECK_EQUAL(ret.first, command); + BOOST_CHECK_EQUAL(ret.second, args); +} + +BOOST_AUTO_TEST_CASE(util_SplitTorReplyLine) +{ + // Data we should receive during normal usage + CheckSplitTorReplyLine( + "PROTOCOLINFO PIVERSION", + "PROTOCOLINFO", "PIVERSION"); + CheckSplitTorReplyLine( + "AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE=\"/home/x/.tor/control_auth_cookie\"", + "AUTH", "METHODS=COOKIE,SAFECOOKIE COOKIEFILE=\"/home/x/.tor/control_auth_cookie\""); + CheckSplitTorReplyLine( + "AUTH METHODS=NULL", + "AUTH", "METHODS=NULL"); + CheckSplitTorReplyLine( + "AUTH METHODS=HASHEDPASSWORD", + "AUTH", "METHODS=HASHEDPASSWORD"); + CheckSplitTorReplyLine( + "VERSION Tor=\"0.2.9.8 (git-a0df013ea241b026)\"", + "VERSION", "Tor=\"0.2.9.8 (git-a0df013ea241b026)\""); + CheckSplitTorReplyLine( + "AUTHCHALLENGE SERVERHASH=aaaa SERVERNONCE=bbbb", + "AUTHCHALLENGE", "SERVERHASH=aaaa SERVERNONCE=bbbb"); + + // Other valid inputs + CheckSplitTorReplyLine("COMMAND", "COMMAND", ""); + CheckSplitTorReplyLine("COMMAND SOME ARGS", "COMMAND", "SOME ARGS"); + + // These inputs are valid because PROTOCOLINFO accepts an OtherLine that is + // just an OptArguments, which enables multiple spaces to be present + // between the command and arguments. + CheckSplitTorReplyLine("COMMAND ARGS", "COMMAND", " ARGS"); + CheckSplitTorReplyLine("COMMAND EVEN+more ARGS", "COMMAND", " EVEN+more ARGS"); +} + +void CheckParseTorReplyMapping(std::string input, std::map expected) +{ + BOOST_TEST_MESSAGE(std::string("CheckParseTorReplyMapping(") + input + ")"); + auto ret = ParseTorReplyMapping(input); + BOOST_CHECK_EQUAL(ret.size(), expected.size()); + auto r_it = ret.begin(); + auto e_it = expected.begin(); + while (r_it != ret.end() && e_it != expected.end()) { + BOOST_CHECK_EQUAL(r_it->first, e_it->first); + BOOST_CHECK_EQUAL(r_it->second, e_it->second); + r_it++; + e_it++; + } +} + +BOOST_AUTO_TEST_CASE(util_ParseTorReplyMapping) +{ + // Data we should receive during normal usage + CheckParseTorReplyMapping( + "METHODS=COOKIE,SAFECOOKIE COOKIEFILE=\"/home/x/.tor/control_auth_cookie\"", { + {"METHODS", "COOKIE,SAFECOOKIE"}, + {"COOKIEFILE", "/home/x/.tor/control_auth_cookie"}, + }); + CheckParseTorReplyMapping( + "METHODS=NULL", { + {"METHODS", "NULL"}, + }); + CheckParseTorReplyMapping( + "METHODS=HASHEDPASSWORD", { + {"METHODS", "HASHEDPASSWORD"}, + }); + CheckParseTorReplyMapping( + "Tor=\"0.2.9.8 (git-a0df013ea241b026)\"", { + {"Tor", "0.2.9.8 (git-a0df013ea241b026)"}, + }); + CheckParseTorReplyMapping( + "SERVERHASH=aaaa SERVERNONCE=bbbb", { + {"SERVERHASH", "aaaa"}, + {"SERVERNONCE", "bbbb"}, + }); + CheckParseTorReplyMapping( + "ServiceID=exampleonion1234", { + {"ServiceID", "exampleonion1234"}, + }); + CheckParseTorReplyMapping( + "PrivateKey=RSA1024:BLOB", { + {"PrivateKey", "RSA1024:BLOB"}, + }); + CheckParseTorReplyMapping( + "ClientAuth=bob:BLOB", { + {"ClientAuth", "bob:BLOB"}, + }); + + // Other valid inputs + CheckParseTorReplyMapping( + "Foo=Bar=Baz Spam=Eggs", { + {"Foo", "Bar=Baz"}, + {"Spam", "Eggs"}, + }); + CheckParseTorReplyMapping( + "Foo=\"Bar=Baz\"", { + {"Foo", "Bar=Baz"}, + }); + CheckParseTorReplyMapping( + "Foo=\"Bar Baz\"", { + {"Foo", "Bar Baz"}, + }); + + // Escapes (which are left escaped by the parser) + CheckParseTorReplyMapping( + "Foo=\"Bar\\ Baz\"", { + {"Foo", "Bar\\ Baz"}, + }); + CheckParseTorReplyMapping( + "Foo=\"Bar\\Baz\"", { + {"Foo", "Bar\\Baz"}, + }); + CheckParseTorReplyMapping( + "Foo=\"Bar\\@Baz\"", { + {"Foo", "Bar\\@Baz"}, + }); + CheckParseTorReplyMapping( + "Foo=\"Bar\\\"Baz\" Spam=\"\\\"Eggs\\\"\"", { + {"Foo", "Bar\\\"Baz"}, + {"Spam", "\\\"Eggs\\\""}, + }); + CheckParseTorReplyMapping( + "Foo=\"Bar\\\\Baz\"", { + {"Foo", "Bar\\\\Baz"}, + }); + + // A more complex valid grammar. PROTOCOLINFO accepts a VersionLine that + // takes a key=value pair followed by an OptArguments, making this valid. + // Because an OptArguments contains no semantic data, there is no point in + // parsing it. + CheckParseTorReplyMapping( + "SOME=args,here MORE optional=arguments here", { + {"SOME", "args,here"}, + }); + + // Inputs that are effectively invalid under the target grammar. + // PROTOCOLINFO accepts an OtherLine that is just an OptArguments, which + // would make these inputs valid. However, + // - This parser is never used in that situation, because the + // SplitTorReplyLine parser enables OtherLine to be skipped. + // - Even if these were valid, an OptArguments contains no semantic data, + // so there is no point in parsing it. + CheckParseTorReplyMapping("ARGS", {}); + CheckParseTorReplyMapping("MORE ARGS", {}); + CheckParseTorReplyMapping("MORE ARGS", {}); + CheckParseTorReplyMapping("EVEN more=ARGS", {}); + CheckParseTorReplyMapping("EVEN+more ARGS", {}); +} + +BOOST_AUTO_TEST_SUITE_END() From 64101d040736e7e0fc12a313c0a5dd7d1f57093f Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 26 Mar 2017 00:35:13 +1300 Subject: [PATCH 26/28] torcontrol: Fix ParseTorReplyMapping - Ignore remaining input if it is an OptArguments - Correctly handle escapes --- src/torcontrol.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 97efe2bb5..9898c508a 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -271,17 +271,19 @@ static std::map ParseTorReplyMapping(const std::string size_t ptr=0; while (ptr < s.size()) { std::string key, value; - while (ptr < s.size() && s[ptr] != '=') { + while (ptr < s.size() && s[ptr] != '=' && s[ptr] != ' ') { key.push_back(s[ptr]); ++ptr; } if (ptr == s.size()) // unexpected end of line return std::map(); + if (s[ptr] == ' ') // The remaining string is an OptArguments + break; ++ptr; // skip '=' if (ptr < s.size() && s[ptr] == '"') { // Quoted string ++ptr; // skip opening '"' bool escape_next = false; - while (ptr < s.size() && (!escape_next && s[ptr] != '"')) { + while (ptr < s.size() && (escape_next || s[ptr] != '"')) { escape_next = (s[ptr] == '\\'); value.push_back(s[ptr]); ++ptr; From 3290567bbd54e01cb6fe6b1d04c659abca983af2 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 26 Mar 2017 13:53:13 +1300 Subject: [PATCH 27/28] torcontrol: Check for reading errors in ReadBinaryFile This ensures that ReadBinaryFile never returns exactly TOR_COOKIE_SIZE bytes if the file was larger than that. --- src/torcontrol.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 9898c508a..e957e38bd 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -324,6 +324,10 @@ static std::pair ReadBinaryFile(const std::string &filename, s char buffer[128]; size_t n; while ((n=fread(buffer, 1, sizeof(buffer), f)) > 0) { + // Check for reading errors so we don't return any data if we couldn't + // read the entire file (or up to maxsize) + if (ferror(f)) + return std::make_pair(false,""); retval.append(buffer, buffer+n); if (retval.size() > maxsize) break; From 87b7f4d878aafb8056df67f6dd622c2e728838c5 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 26 Mar 2017 14:35:13 +1300 Subject: [PATCH 28/28] torcontrol: Log invalid parameters in Tor reply strings where meaningful --- src/torcontrol.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index e957e38bd..60181b440 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -261,6 +261,7 @@ static std::pair SplitTorReplyLine(const std::string &s } /** Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE COOKIEFILE=".../control_auth_cookie"'. + * Returns a map of keys to values, or an empty map if there was an error. * Grammar is implicitly defined in https://spec.torproject.org/control-spec by * the server reply formats for PROTOCOLINFO (S3.21), AUTHCHALLENGE (S3.24), * and ADD_ONION (S3.27). See also sections 2.1 and 2.3. @@ -444,6 +445,13 @@ void TorController::add_onion_cb(TorControlConnection& conn, const TorControlRep if ((i = m.find("PrivateKey")) != m.end()) private_key = i->second; } + if (service_id.empty()) { + LogPrintf("tor: Error parsing ADD_ONION parameters:\n"); + for (const std::string &s : reply.lines) { + LogPrintf(" %s\n", SanitizeString(s)); + } + return; + } service = CService(service_id+".onion", GetListenPort(), false); LogPrintf("tor: Got service ID %s, advertizing service %s\n", service_id, service.ToString()); @@ -521,6 +529,10 @@ void TorController::authchallenge_cb(TorControlConnection& conn, const TorContro std::pair l = SplitTorReplyLine(reply.lines[0]); if (l.first == "AUTHCHALLENGE") { std::map m = ParseTorReplyMapping(l.second); + if (m.empty()) { + LogPrintf("tor: Error parsing AUTHCHALLENGE parameters: %s\n", SanitizeString(l.second)); + return; + } std::vector serverHash = ParseHex(m["SERVERHASH"]); std::vector serverNonce = ParseHex(m["SERVERNONCE"]); LogPrint("tor", "tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", HexStr(serverHash), HexStr(serverNonce));