Skip to content

Commit

Permalink
Merge bitcoin#16248: Make whitebind/whitelist permissions more flexible
Browse files Browse the repository at this point in the history
c5b404e Add functional tests for flexible whitebind/list (nicolas.dorier)
d541fa3 Replace the use of fWhitelisted by permission checks (nicolas.dorier)
ecd5cf7 Do not disconnect peer for asking mempool if it has NO_BAN permission (nicolas.dorier)
e5b26de Make whitebind/whitelist permissions more flexible (nicolas.dorier)

Pull request description:

  # Motivation

  In 0.19, bloom filter will be disabled by default. I tried to make [a PR](bitcoin#16176) to enable bloom filter for whitelisted peers regardless of `-peerbloomfilters`.

  Bloom filter have non existent privacy and server can omit filter's matches. However, both problems are completely irrelevant when you connect to your own node. If you connect to your own node, bloom filters are the most bandwidth efficient way to synchronize your light client without the need of some middleware like Electrum.

  It is also a superior alternative to BIP157 as it does not require to maintain an additional index and it would work well on pruned nodes.

  When I attempted to allow bloom filters for whitelisted peer, my proposal has been NACKed in favor of [a more flexible approach](bitcoin#16176 (comment)) which should allow node operator to set fine grained permissions instead of a global `whitelisted` attribute.

  Doing so will also make follow up idea very easy to implement in a backward compatible way.

  # Implementation details

  The PR propose a new format for `--white{list,bind}`. I added a way to specify permissions granted to inbound connection matching `white{list,bind}`.

  The following permissions exists:
  * ForceRelay
  * Relay
  * NoBan
  * BloomFilter
  * Mempool

  Example:
  * `-whitelist=bloomfilter@127.0.0.1/32`.
  * `-whitebind=bloomfilter,relay,noban@127.0.0.1:10020`.

  If no permissions are specified, `NoBan | Mempool` is assumed. (making this PR backward compatible)

  When we receive an inbound connection, we calculate the effective permissions for this peer by fetching the permissions granted from `whitelist`  and add to it the permissions granted from `whitebind`.

  To keep backward compatibility, if no permissions are specified in `white{list,bind}` (e.g. `--whitelist=127.0.0.1`) then parameters `-whitelistforcerelay` and `-whiterelay` will add the permissions `ForceRelay` and `Relay` to the inbound node.

  `-whitelistforcerelay` and `-whiterelay` are ignored if the permissions flags are explicitly set in `white{bind,list}`.

  # Follow up idea

  Based on this PR, other changes become quite easy to code in a trivially review-able, backward compatible way:

  * Changing `connect` at rpc and config file level to understand the permissions flags.
  * Changing the permissions of a peer at RPC level.

ACKs for top commit:
  laanwj:
    re-ACK c5b404e

Tree-SHA512: adfefb373d09e68cae401247c8fc64034e305694cdef104bdcdacb9f1704277bd53b18f52a2427a5cffdbc77bda410d221aed252bc2ece698ffbb9cf1b830577
  • Loading branch information
laanwj authored and PastaPastaPasta committed Jul 18, 2021
1 parent 1988800 commit 166d862
Show file tree
Hide file tree
Showing 13 changed files with 449 additions and 65 deletions.
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ BITCOIN_CORE_H = \
messagesigner.h \
miner.h \
net.h \
net_permissions.h \
net_processing.h \
netaddress.h \
netbase.h \
Expand Down Expand Up @@ -570,6 +571,7 @@ libdash_common_a_SOURCES = \
keystore.cpp \
netaddress.cpp \
netbase.cpp \
net_permissions.cpp \
policy/feerate.cpp \
protocol.cpp \
saltedhasher.cpp \
Expand Down
20 changes: 8 additions & 12 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <miner.h>
#include <netbase.h>
#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
#include <policy/feerate.h>
#include <policy/fees.h>
Expand Down Expand Up @@ -2527,21 +2528,16 @@ bool AppInitMain()
connOptions.vBinds.push_back(addrBind);
}
for (const std::string& strBind : gArgs.GetArgs("-whitebind")) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, 0, false)) {
return InitError(ResolveErrMsg("whitebind", strBind));
}
if (addrBind.GetPort() == 0) {
return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind));
}
connOptions.vWhiteBinds.push_back(addrBind);
NetWhitebindPermissions whitebind;
std::string error;
if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) return InitError(error);
connOptions.vWhiteBinds.push_back(whitebind);
}

for (const auto& net : gArgs.GetArgs("-whitelist")) {
CSubNet subnet;
LookupSubNet(net.c_str(), subnet);
if (!subnet.IsValid())
return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net));
NetWhitelistPermissions subnet;
std::string error;
if (!NetWhitelistPermissions::TryParse(net, subnet, error)) return InitError(error);
connOptions.vWhitelistedRange.push_back(subnet);
}

Expand Down
63 changes: 41 additions & 22 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <consensus/consensus.h>
#include <crypto/common.h>
#include <crypto/sha256.h>
#include <net_permissions.h>
#include <primitives/transaction.h>
#include <netbase.h>
#include <scheduler.h>
Expand Down Expand Up @@ -98,7 +99,6 @@ enum BindFlags {
BF_NONE = 0,
BF_EXPLICIT = (1U << 0),
BF_REPORT_ERROR = (1U << 1),
BF_WHITELIST = (1U << 2),
};

#ifndef USE_WAKEUP_PIPE
Expand Down Expand Up @@ -539,12 +539,10 @@ void CNode::CloseSocketDisconnect(CConnman* connman)
statsClient.inc("peers.disconnect", 1.0f);
}

bool CConnman::IsWhitelistedRange(const CNetAddr &addr) {
for (const CSubNet& subnet : vWhitelistedRange) {
if (subnet.Match(addr))
return true;
void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const {
for (const auto& subnet : vWhitelistedRange) {
if (subnet.m_subnet.Match(addr)) NetPermissions::AddFlag(flags, subnet.m_flags);
}
return false;
}

std::string CNode::GetAddrName() const {
Expand Down Expand Up @@ -614,7 +612,8 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap)
X(mapRecvBytesPerMsgCmd);
X(nRecvBytes);
}
X(fWhitelisted);
X(m_legacyWhitelisted);
X(m_permissionFlags);

// It is common for nodes with good ping times to suddenly become lagged,
// due to a new block arriving or other large transfer.
Expand Down Expand Up @@ -907,7 +906,7 @@ bool CConnman::AttemptToEvictConnection()
LOCK(cs_vNodes);

for (const CNode* node : vNodes) {
if (node->fWhitelisted)
if (node->HasPermission(PF_NOBAN))
continue;
if (!node->fInbound)
continue;
Expand Down Expand Up @@ -1018,7 +1017,20 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
}
}

bool whitelisted = hListenSocket.whitelisted || IsWhitelistedRange(addr);
NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE;
hListenSocket.AddSocketPermissionFlags(permissionFlags);
AddWhitelistPermissionFlags(permissionFlags, addr);
const bool noban = NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN);
bool legacyWhitelisted = false;
if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_ISIMPLICIT)) {
NetPermissions::ClearFlag(permissionFlags, PF_ISIMPLICIT);
if (gArgs.GetBoolArg("-whitelistforcerelay", false)) NetPermissions::AddFlag(permissionFlags, PF_FORCERELAY);
if (gArgs.GetBoolArg("-whitelistrelay", false)) NetPermissions::AddFlag(permissionFlags, PF_RELAY);
NetPermissions::AddFlag(permissionFlags, PF_MEMPOOL);
NetPermissions::AddFlag(permissionFlags, PF_NOBAN);
legacyWhitelisted = true;
}

{
LOCK(cs_vNodes);
for (const CNode* pnode : vNodes) {
Expand Down Expand Up @@ -1068,7 +1080,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {

// Don't accept connections from banned peers, but if our inbound slots aren't almost full, accept
// if the only banning reason was an automatic misbehavior ban.
if (!whitelisted && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0))
if (!noban && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0))
{
LogPrint(BCLog::NET, "%s (banned)\n", strDropped);
CloseSocket(hSocket);
Expand Down Expand Up @@ -1102,9 +1114,15 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
CAddress addr_bind = GetBindAddress(hSocket);

CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true);
ServiceFlags nodeServices = nLocalServices;
if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) {
nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM);
}
CNode* pnode = new CNode(id, nodeServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true);
pnode->AddRef();
pnode->fWhitelisted = whitelisted;
pnode->m_permissionFlags = permissionFlags;
// If this flag is present, the user probably expect that RPC and QT report it as whitelisted (backward compatibility)
pnode->m_legacyWhitelisted = legacyWhitelisted;
pnode->m_prefer_evict = bannedlevel > 0;
m_msgproc->InitializeNode(pnode);

Expand Down Expand Up @@ -2702,7 +2720,7 @@ void CConnman::ThreadMessageHandler()



bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, bool fWhitelisted)
bool CConnman::BindListenPort(const CService& addrBind, std::string& strError, NetPermissionFlags permissions)
{
strError = "";
int nOne = 1;
Expand Down Expand Up @@ -2790,9 +2808,9 @@ bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, b
}
#endif

vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted));
vhListenSocket.push_back(ListenSocket(hListenSocket, permissions));

if (addrBind.IsRoutable() && fDiscover && !fWhitelisted)
if (addrBind.IsRoutable() && fDiscover && (permissions & PF_NOBAN) == 0)
AddLocal(addrBind, LOCAL_BIND);

return true;
Expand Down Expand Up @@ -2889,11 +2907,11 @@ NodeId CConnman::GetNewNodeId()
}


bool CConnman::Bind(const CService &addr, unsigned int flags) {
bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions) {
if (!(flags & BF_EXPLICIT) && !IsReachable(addr))
return false;
std::string strError;
if (!BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) {
if (!BindListenPort(addr, strError, permissions)) {
if ((flags & BF_REPORT_ERROR) && clientInterface) {
clientInterface->ThreadSafeMessageBox(strError, "", CClientUIInterface::MSG_ERROR);
}
Expand All @@ -2902,20 +2920,21 @@ bool CConnman::Bind(const CService &addr, unsigned int flags) {
return true;
}

bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<CService>& whiteBinds) {
bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds)
{
bool fBound = false;
for (const auto& addrBind : binds) {
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR));
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::PF_NONE);
}
for (const auto& addrBind : whiteBinds) {
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST));
fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags);
}
if (binds.empty() && whiteBinds.empty()) {
struct in_addr inaddr_any;
inaddr_any.s_addr = INADDR_ANY;
struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT;
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE);
fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE);
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::PF_NONE);
fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::PF_NONE);
}
return fBound;
}
Expand Down
35 changes: 22 additions & 13 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <hash.h>
#include <limitedmap.h>
#include <netaddress.h>
#include <net_permissions.h>
#include <policy/feerate.h>
#include <protocol.h>
#include <random.h>
Expand Down Expand Up @@ -171,8 +172,9 @@ friend class CNode;
uint64_t nMaxOutboundLimit = 0;
int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT;
std::vector<std::string> vSeedNodes;
std::vector<CSubNet> vWhitelistedRange;
std::vector<CService> vBinds, vWhiteBinds;
std::vector<NetWhitelistPermissions> vWhitelistedRange;
std::vector<NetWhitebindPermissions> vWhiteBinds;
std::vector<CService> vBinds;
bool m_use_addrman_outgoing = true;
std::vector<std::string> m_specified_outgoing;
std::vector<std::string> m_added_nodes;
Expand Down Expand Up @@ -472,15 +474,17 @@ friend class CNode;

private:
struct ListenSocket {
public:
SOCKET socket;
bool whitelisted;

ListenSocket(SOCKET socket_, bool whitelisted_) : socket(socket_), whitelisted(whitelisted_) {}
inline void AddSocketPermissionFlags(NetPermissionFlags& flags) const { NetPermissions::AddFlag(flags, m_permissions); }
ListenSocket(SOCKET socket_, NetPermissionFlags permissions_) : socket(socket_), m_permissions(permissions_) {}
private:
NetPermissionFlags m_permissions;
};

bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false);
bool Bind(const CService &addr, unsigned int flags);
bool InitBinds(const std::vector<CService>& binds, const std::vector<CService>& whiteBinds);
bool BindListenPort(const CService& bindAddr, std::string& strError, NetPermissionFlags permissions);
bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions);
bool InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds);
void ThreadOpenAddedConnections();
void AddOneShot(const std::string& strDest);
void ProcessOneShot();
Expand Down Expand Up @@ -517,7 +521,7 @@ friend class CNode;

bool AttemptToEvictConnection();
CNode* ConnectNode(CAddress addrConnect, const char *pszDest = nullptr, bool fCountFailure = false, bool manual_connection = false);
bool IsWhitelistedRange(const CNetAddr &addr);
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const;

void DeleteNode(CNode* pnode);

Expand Down Expand Up @@ -554,7 +558,7 @@ friend class CNode;

// Whitelisted ranges. Any node connecting from these is automatically
// whitelisted (as well as those connecting to whitelisted binds).
std::vector<CSubNet> vWhitelistedRange;
std::vector<NetWhitelistPermissions> vWhitelistedRange;

unsigned int nSendBufferMaxSize;
unsigned int nReceiveFloodSize;
Expand Down Expand Up @@ -650,7 +654,6 @@ void StartMapPort();
void InterruptMapPort();
void StopMapPort();
unsigned short GetListenPort();
bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false);

struct CombinerAll
{
Expand Down Expand Up @@ -755,7 +758,8 @@ class CNodeStats
mapMsgCmdSize mapSendBytesPerMsgCmd;
uint64_t nRecvBytes;
mapMsgCmdSize mapRecvBytesPerMsgCmd;
bool fWhitelisted;
NetPermissionFlags m_permissionFlags;
bool m_legacyWhitelisted;
double dPingTime;
double dPingWait;
double dMinPing;
Expand Down Expand Up @@ -867,7 +871,11 @@ class CNode
std::string cleanSubVer GUARDED_BY(cs_SubVer){};
CCriticalSection cs_SubVer; // used for both cleanSubVer and strSubVer
bool m_prefer_evict{false}; // This peer is preferred for eviction.
bool fWhitelisted; // This peer can bypass DoS banning.
bool HasPermission(NetPermissionFlags permission) const {
return NetPermissions::HasFlag(m_permissionFlags, permission);
}
// This boolean is unusued in actual processing, only present for backward compatibility at RPC/QT level
bool m_legacyWhitelisted{false};
bool fFeeler; // If true this node is being used as a short lived feeler.
bool fOneShot;
bool m_manual_connection;
Expand Down Expand Up @@ -993,6 +1001,7 @@ class CNode
const ServiceFlags nLocalServices;
const int nMyStartingHeight;
int nSendVersion;
NetPermissionFlags m_permissionFlags{ PF_NONE };
std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread

mutable CCriticalSection cs_addrName;
Expand Down
Loading

0 comments on commit 166d862

Please sign in to comment.