Introduce whitelisted peers.

This adds a -whitelist option to specify subnet ranges from which peers
that connect are whitelisted. In addition, there is a -whitebind option
which works like -bind, except peers connecting to it are also
whitelisted (allowing a separate listen port for trusted connections).

Being whitelisted has two effects (for now):
* They are immune to DoS disconnection/banning.
* Transactions they broadcast (which are valid) are always relayed,
  even if they were already in the mempool. This means that a node
  can function as a gateway for a local network, and that rebroadcasts
  from the local network will work as expected.

Whitelisting replaces the magic exemption localhost had for DoS
disconnection (local addresses are still never banned, though), which
implied hidden service connects (from a localhost Tor node) were
incorrectly immune to DoS disconnection as well. This old
behaviour is removed for that reason, but can be restored using
-whitelist=127.0.0.1 or -whitelist=::1 can be specified. -whitebind
is safer to use in case non-trusted localhost connections are expected
(like hidden services).
This commit is contained in:
Pieter Wuille 2014-06-21 13:34:36 +02:00 committed by Ross Nicoll
parent 33333d2e88
commit 6f86ee9cae
6 changed files with 101 additions and 29 deletions

View file

@ -10,7 +10,7 @@ touch "$DATADIR/regtest/debug.log"
tail -q -n 1 -F "$DATADIR/regtest/debug.log" | grep -m 1 -q "Done loading" & tail -q -n 1 -F "$DATADIR/regtest/debug.log" | grep -m 1 -q "Done loading" &
WAITER=$! WAITER=$!
PORT=`expr $BASHPID + 10000` PORT=`expr $BASHPID + 10000`
"@abs_top_builddir@/src/bitcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -port=$PORT -regtest -rpcport=`expr $PORT + 1` & "@abs_top_builddir@/src/bitcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -port=$PORT -whitelist=127.0.0.1 -regtest -rpcport=`expr $PORT + 1` &
BITCOIND=$! BITCOIND=$!
#Install a watchdog. #Install a watchdog.

View file

@ -62,7 +62,8 @@ CWallet* pwalletMain;
enum BindFlags { enum BindFlags {
BF_NONE = 0, BF_NONE = 0,
BF_EXPLICIT = (1U << 0), BF_EXPLICIT = (1U << 0),
BF_REPORT_ERROR = (1U << 1) BF_REPORT_ERROR = (1U << 1),
BF_WHITELIST = (1U << 2),
}; };
@ -183,7 +184,7 @@ bool static Bind(const CService &addr, unsigned int flags) {
if (!(flags & BF_EXPLICIT) && IsLimited(addr)) if (!(flags & BF_EXPLICIT) && IsLimited(addr))
return false; return false;
std::string strError; std::string strError;
if (!BindListenPort(addr, strError)) { if (!BindListenPort(addr, strError, flags & BF_WHITELIST)) {
if (flags & BF_REPORT_ERROR) if (flags & BF_REPORT_ERROR)
return InitError(strError); return InitError(strError);
return false; return false;
@ -246,6 +247,8 @@ std::string HelpMessage(HelpMessageMode hmm)
strUsage += " -upnp " + _("Use UPnP to map the listening port (default: 0)") + "\n"; strUsage += " -upnp " + _("Use UPnP to map the listening port (default: 0)") + "\n";
#endif #endif
#endif #endif
strUsage += " -whitebind=<addr> " + _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6") + "\n";
strUsage += " -whitelist=<netmask> " + _("Whitelist peers connecting from the given netmask or ip. Can be specified multiple times.") + "\n";
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
strUsage += "\n" + _("Wallet options:") + "\n"; strUsage += "\n" + _("Wallet options:") + "\n";
@ -478,11 +481,11 @@ bool AppInit2(boost::thread_group& threadGroup)
nLocalServices |= NODE_BLOOM; nLocalServices |= NODE_BLOOM;
} }
if (mapArgs.count("-bind")) { if (mapArgs.count("-bind") || mapArgs.count("-whitebind")) {
// when specifying an explicit binding address, you want to listen on it // when specifying an explicit binding address, you want to listen on it
// even when -connect or -proxy is specified // even when -connect or -proxy is specified
if (SoftSetBoolArg("-listen", true)) if (SoftSetBoolArg("-listen", true))
LogPrintf("AppInit2 : parameter interaction: -bind set -> setting -listen=1\n"); LogPrintf("AppInit2 : parameter interaction: -bind or -whitebind set -> setting -listen=1\n");
} }
if (mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0) { if (mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0) {
@ -526,7 +529,7 @@ bool AppInit2(boost::thread_group& threadGroup)
} }
// Make sure enough file descriptors are available // Make sure enough file descriptors are available
int nBind = std::max((int)mapArgs.count("-bind"), 1); int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-whitebind"), 1);
nMaxConnections = GetArg("-maxconnections", 125); nMaxConnections = GetArg("-maxconnections", 125);
nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0);
int nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS); int nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS);
@ -752,6 +755,15 @@ bool AppInit2(boost::thread_group& threadGroup)
} }
} }
if (mapArgs.count("-whitelist")) {
BOOST_FOREACH(const std::string& net, mapMultiArgs["-whitelist"]) {
CSubNet subnet(net);
if (!subnet.IsValid())
return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net));
CNode::AddWhitelistedRange(subnet);
}
}
CService addrProxy; CService addrProxy;
bool fProxy = false; bool fProxy = false;
if (mapArgs.count("-proxy")) { if (mapArgs.count("-proxy")) {
@ -792,13 +804,21 @@ bool AppInit2(boost::thread_group& threadGroup)
bool fBound = false; bool fBound = false;
if (fListen) { if (fListen) {
if (mapArgs.count("-bind")) { if (mapArgs.count("-bind") || mapArgs.count("-whitebind")) {
BOOST_FOREACH(std::string strBind, mapMultiArgs["-bind"]) { BOOST_FOREACH(std::string strBind, mapMultiArgs["-bind"]) {
CService addrBind; CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false))
return InitError(strprintf(_("Cannot resolve -bind address: '%s'"), strBind)); return InitError(strprintf(_("Cannot resolve -bind address: '%s'"), strBind));
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR));
} }
BOOST_FOREACH(std::string strBind, mapMultiArgs["-whitebind"]) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, 0, false))
return InitError(strprintf(_("Cannot resolve -whitebind address: '%s'"), strBind));
if (addrBind.GetPort() == 0)
return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind));
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST));
}
} }
else { else {
struct in_addr inaddr_any; struct in_addr inaddr_any;

View file

@ -209,7 +209,7 @@ struct CBlockReject {
struct CNodeState { struct CNodeState {
// Accumulated misbehaviour score for this peer. // Accumulated misbehaviour score for this peer.
int nMisbehavior; int nMisbehavior;
// Whether this peer should be disconnected and banned. // Whether this peer should be disconnected and banned (unless whitelisted).
bool fShouldBan; bool fShouldBan;
// String name of this peer (debugging/logging purposes). // String name of this peer (debugging/logging purposes).
std::string name; std::string name;
@ -1571,7 +1571,8 @@ void Misbehaving(NodeId pnode, int howmuch)
return; return;
state->nMisbehavior += howmuch; state->nMisbehavior += howmuch;
if (state->nMisbehavior >= GetArg("-banscore", 100)) int banscore = GetArg("-banscore", 100);
if (state->nMisbehavior >= banscore && state->nMisbehavior - howmuch < banscore)
{ {
LogPrintf("Misbehaving: %s (%d -> %d) BAN THRESHOLD EXCEEDED\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior); LogPrintf("Misbehaving: %s (%d -> %d) BAN THRESHOLD EXCEEDED\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior);
state->fShouldBan = true; state->fShouldBan = true;
@ -4163,6 +4164,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
if (nEvicted > 0) if (nEvicted > 0)
LogPrint("mempool", "mapOrphan overflow, removed %u tx\n", nEvicted); LogPrint("mempool", "mapOrphan overflow, removed %u tx\n", nEvicted);
} else if (pfrom->fWhitelisted) {
// Always relay transactions received from whitelisted peers, even
// if they are already in the mempool (allowing the node to function
// as a gateway for nodes hidden behind it).
RelayTransaction(tx, tx.GetHash());
} }
int nDoS = 0; int nDoS = 0;
if (state.IsInvalid(nDoS)) if (state.IsInvalid(nDoS))
@ -4649,11 +4655,14 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
CNodeState &state = *State(pto->GetId()); CNodeState &state = *State(pto->GetId());
if (state.fShouldBan) { if (state.fShouldBan) {
if (pto->addr.IsLocal()) if (pto->fWhitelisted)
LogPrintf("Warning: not banning local node %s!\n", pto->addr.ToString()); LogPrintf("Warning: not punishing whitelisted peer %s!\n", pto->addr.ToString());
else { else {
pto->fDisconnect = true; pto->fDisconnect = true;
CNode::Ban(pto->addr); if (pto->addr.IsLocal())
LogPrintf("Warning: not banning local peer %s!\n", pto->addr.ToString());
else
CNode::Ban(pto->addr);
} }
state.fShouldBan = false; state.fShouldBan = false;
} }

View file

@ -50,6 +50,17 @@
using namespace std; using namespace std;
using namespace boost; using namespace boost;
namespace {
const int MAX_OUTBOUND_CONNECTIONS = 8;
struct ListenSocket {
SOCKET socket;
bool whitelisted;
ListenSocket(SOCKET socket, bool whitelisted) : socket(socket), whitelisted(whitelisted) {}
};
}
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false); bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false);
@ -66,7 +77,7 @@ static bool vfLimited[NET_MAX] = {};
static CNode* pnodeLocalHost = NULL; static CNode* pnodeLocalHost = NULL;
static CNode* pnodeSync = NULL; static CNode* pnodeSync = NULL;
uint64_t nLocalHostNonce = 0; uint64_t nLocalHostNonce = 0;
static std::vector<SOCKET> vhListenSocket; static std::vector<ListenSocket> vhListenSocket;
CAddrMan addrman; CAddrMan addrman;
int nMaxConnections = 125; int nMaxConnections = 125;
int nMaxOutboundConnections = 8; int nMaxOutboundConnections = 8;
@ -577,6 +588,24 @@ bool CNode::Ban(const CNetAddr &addr) {
return true; return true;
} }
std::vector<CSubNet> CNode::vWhitelistedRange;
CCriticalSection CNode::cs_vWhitelistedRange;
bool CNode::IsWhitelistedRange(const CNetAddr &addr) {
LOCK(cs_vWhitelistedRange);
BOOST_FOREACH(const CSubNet& subnet, vWhitelistedRange) {
if (subnet.Match(addr))
return true;
}
return false;
}
void CNode::AddWhitelistedRange(const CSubNet &subnet) {
LOCK(cs_vWhitelistedRange);
vWhitelistedRange.push_back(subnet);
}
#undef X #undef X
#define X(name) stats.name = name #define X(name) stats.name = name
void CNode::copyStats(CNodeStats &stats) void CNode::copyStats(CNodeStats &stats)
@ -593,6 +622,7 @@ void CNode::copyStats(CNodeStats &stats)
X(nStartingHeight); X(nStartingHeight);
X(nSendBytes); X(nSendBytes);
X(nRecvBytes); X(nRecvBytes);
X(fWhitelisted);
stats.fSyncNode = (this == pnodeSync); stats.fSyncNode = (this == pnodeSync);
// It is common for nodes with good ping times to suddenly become lagged, // It is common for nodes with good ping times to suddenly become lagged,
@ -828,9 +858,9 @@ void ThreadSocketHandler()
SOCKET hSocketMax = 0; SOCKET hSocketMax = 0;
bool have_fds = false; bool have_fds = false;
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) { BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocket) {
FD_SET(hListenSocket, &fdsetRecv); FD_SET(hListenSocket.socket, &fdsetRecv);
hSocketMax = max(hSocketMax, hListenSocket); hSocketMax = max(hSocketMax, hListenSocket.socket);
have_fds = true; have_fds = true;
} }
@ -897,13 +927,13 @@ void ThreadSocketHandler()
// //
// Accept new connections // Accept new connections
// //
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocket)
{ {
if (hListenSocket != INVALID_SOCKET && FD_ISSET(hListenSocket, &fdsetRecv)) if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv))
{ {
struct sockaddr_storage sockaddr; struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr); socklen_t len = sizeof(sockaddr);
SOCKET hSocket = accept(hListenSocket, (struct sockaddr*)&sockaddr, &len); SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
CAddress addr; CAddress addr;
int nInbound = 0; int nInbound = 0;
@ -911,6 +941,7 @@ void ThreadSocketHandler()
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr))
LogPrintf("Warning: Unknown socket family\n"); LogPrintf("Warning: Unknown socket family\n");
bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr);
{ {
LOCK(cs_vNodes); LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes) BOOST_FOREACH(CNode* pnode, vNodes)
@ -928,7 +959,7 @@ void ThreadSocketHandler()
{ {
closesocket(hSocket); closesocket(hSocket);
} }
else if (CNode::IsBanned(addr)) else if (CNode::IsBanned(addr) && !whitelisted)
{ {
LogPrintf("connection from %s dropped (banned)\n", addr.ToString()); LogPrintf("connection from %s dropped (banned)\n", addr.ToString());
closesocket(hSocket); closesocket(hSocket);
@ -938,6 +969,7 @@ void ThreadSocketHandler()
LogPrint("net", "accepted connection %s\n", addr.ToString()); LogPrint("net", "accepted connection %s\n", addr.ToString());
CNode* pnode = new CNode(hSocket, addr, "", true); CNode* pnode = new CNode(hSocket, addr, "", true);
pnode->AddRef(); pnode->AddRef();
pnode->fWhitelisted = whitelisted;
{ {
LOCK(cs_vNodes); LOCK(cs_vNodes);
@ -1571,7 +1603,7 @@ void ThreadMessageHandler()
bool BindListenPort(const CService &addrBind, string& strError) bool BindListenPort(const CService &addrBind, string& strError, bool fWhitelisted)
{ {
strError = ""; strError = "";
int nOne = 1; int nOne = 1;
@ -1652,9 +1684,9 @@ bool BindListenPort(const CService &addrBind, string& strError)
return false; return false;
} }
vhListenSocket.push_back(hListenSocket); vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted));
if (addrBind.IsRoutable() && fDiscover) if (addrBind.IsRoutable() && fDiscover && !fWhitelisted)
AddLocal(addrBind, LOCAL_BIND); AddLocal(addrBind, LOCAL_BIND);
return true; return true;
@ -1782,9 +1814,9 @@ public:
BOOST_FOREACH(CNode* pnode, vNodes) BOOST_FOREACH(CNode* pnode, vNodes)
if (pnode->hSocket != INVALID_SOCKET) if (pnode->hSocket != INVALID_SOCKET)
closesocket(pnode->hSocket); closesocket(pnode->hSocket);
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) BOOST_FOREACH(ListenSocket& hListenSocket, vhListenSocket)
if (hListenSocket != INVALID_SOCKET) if (hListenSocket.socket != INVALID_SOCKET)
if (closesocket(hListenSocket) == SOCKET_ERROR) if (closesocket(hListenSocket.socket) == SOCKET_ERROR)
LogPrintf("closesocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); LogPrintf("closesocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError()));
// clean up some globals (to help leak detection) // clean up some globals (to help leak detection)

View file

@ -58,7 +58,7 @@ CNode* FindNode(const CService& ip);
CNode* ConnectNode(CAddress addrConnect, const char *strDest = NULL); CNode* ConnectNode(CAddress addrConnect, const char *strDest = NULL);
void MapPort(bool fUseUPnP); void MapPort(bool fUseUPnP);
unsigned short GetListenPort(); unsigned short GetListenPort();
bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string())); bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false);
void StartNode(boost::thread_group& threadGroup); void StartNode(boost::thread_group& threadGroup);
bool StopNode(); bool StopNode();
void SocketSendData(CNode *pnode); void SocketSendData(CNode *pnode);
@ -149,6 +149,7 @@ public:
uint64_t nSendBytes; uint64_t nSendBytes;
uint64_t nRecvBytes; uint64_t nRecvBytes;
bool fSyncNode; bool fSyncNode;
bool fWhitelisted;
double dPingTime; double dPingTime;
double dPingWait; double dPingWait;
std::string addrLocal; std::string addrLocal;
@ -231,6 +232,7 @@ public:
// store the sanitized version in cleanSubVer. The original should be used when dealing with // store the sanitized version in cleanSubVer. The original should be used when dealing with
// the network or wire types and the cleaned string used when displayed or logged. // the network or wire types and the cleaned string used when displayed or logged.
std::string strSubVer, cleanSubVer; std::string strSubVer, cleanSubVer;
bool fWhitelisted; // This peer can bypass DoS banning.
bool fOneShot; bool fOneShot;
bool fClient; bool fClient;
bool fInbound; bool fInbound;
@ -254,6 +256,11 @@ protected:
static std::map<CNetAddr, int64_t> setBanned; static std::map<CNetAddr, int64_t> setBanned;
static CCriticalSection cs_setBanned; static CCriticalSection cs_setBanned;
// Whitelisted ranges. Any node connecting from these is automatically
// whitelisted (as well as those connecting to whitelisted binds).
static std::vector<CSubNet> vWhitelistedRange;
static CCriticalSection cs_vWhitelistedRange;
// Basic fuzz-testing // Basic fuzz-testing
void Fuzz(int nChance); // modifies ssSend void Fuzz(int nChance); // modifies ssSend
@ -300,6 +307,7 @@ public:
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
nVersion = 0; nVersion = 0;
strSubVer = ""; strSubVer = "";
fWhitelisted = false;
fOneShot = false; fOneShot = false;
fClient = false; // set by version message fClient = false; // set by version message
fInbound = fInboundIn; fInbound = fInboundIn;
@ -713,6 +721,9 @@ public:
static bool Ban(const CNetAddr &ip); static bool Ban(const CNetAddr &ip);
void copyStats(CNodeStats &stats); void copyStats(CNodeStats &stats);
static bool IsWhitelistedRange(const CNetAddr &ip);
static void AddWhitelistedRange(const CSubNet &subnet);
// Network stats // Network stats
static void RecordBytesRecv(uint64_t bytes); static void RecordBytesRecv(uint64_t bytes);
static void RecordBytesSent(uint64_t bytes); static void RecordBytesSent(uint64_t bytes);

View file

@ -134,8 +134,8 @@ Value getpeerinfo(const Array& params, bool fHelp)
if (fStateStats) { if (fStateStats) {
obj.push_back(Pair("banscore", statestats.nMisbehavior)); obj.push_back(Pair("banscore", statestats.nMisbehavior));
} }
if (stats.fSyncNode) obj.push_back(Pair("syncnode", stats.fSyncNode));
obj.push_back(Pair("syncnode", true)); obj.push_back(Pair("whitelisted", stats.fWhitelisted));
ret.push_back(obj); ret.push_back(obj);
} }