Skip to content

Commit

Permalink
Accept "in" and "out" flags to -whitelist to allow whitelisting outgo…
Browse files Browse the repository at this point in the history
…ing connections

Github-Pull: bitcoin#27114
Rebased-From: 22e64e8
  • Loading branch information
luke-jr committed Oct 19, 2023
1 parent c1bd439 commit 7ca2624
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 22 deletions.
22 changes: 15 additions & 7 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
argsman.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from inbound peers is disabled, unless the peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from any peer is disabled, unless it has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-coinstatsindex", strprintf("Maintain coinstats index used by the gettxoutsetinfo RPC (default: %u)", DEFAULT_COINSTATSINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location (only useable from command line, not configuration file) (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
Expand Down Expand Up @@ -520,9 +520,11 @@ void SetupServerArgs(ArgsManager& argsman)
"Use [host]:port notation for IPv6. Allowed permissions: " + Join(NET_PERMISSIONS_DOC, ", ") + ". "
"Specify multiple permissions separated by commas (default: download,noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);

argsman.AddArg("-whitelist=<[permissions@]IP address or network>", "Add permission flags to the peers connecting from the given IP address (e.g. 1.2.3.4) or "
argsman.AddArg("-whitelist=<[permissions@]IP address or network>", "Add permission flags to the peers using the given IP address (e.g. 1.2.3.4) or "
"CIDR-notated network (e.g. 1.2.3.0/24). Uses the same permissions as "
"-whitebind. Can be specified multiple times." , ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
"-whitebind. "
"Additional flags \"in\" and \"out\" control whether permissions apply to incoming connections and/or outgoing (default: incoming only). "
"Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);

g_wallet_init_interface.AddWalletOptions(argsman);

Expand Down Expand Up @@ -587,8 +589,8 @@ void SetupServerArgs(ArgsManager& argsman)
OptionsCategory::NODE_RELAY);
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted inbound peers with default permissions. This will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted peers with default permissions. This will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);


argsman.AddArg("-blockmaxweight=<n>", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
Expand Down Expand Up @@ -1809,10 +1811,16 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

for (const auto& net : args.GetArgs("-whitelist")) {
NetWhitelistPermissions subnet;
ConnectionDirection connection_direction;
bilingual_str error;
if (!NetWhitelistPermissions::TryParse(net, subnet, error)) return InitError(error);
if (!NetWhitelistPermissions::TryParse(net, subnet, connection_direction, error)) return InitError(error);
NetPermissions::AddFlag(all_permission_flags, subnet.m_flags);
connOptions.vWhitelistedRange.push_back(subnet);
if (connection_direction & ConnectionDirection::In) {
connOptions.vWhitelistedRange.push_back(subnet);
}
if (connection_direction & ConnectionDirection::Out) {
connOptions.vWhitelistedRangeOutgoing.push_back(subnet);
}
}

if (NetPermissions::HasFlag(all_permission_flags, NetPermissionFlags::BlockFilters_Explicit)) {
Expand Down
13 changes: 9 additions & 4 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,10 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
return nullptr;
}

NetPermissionFlags permission_flags = NetPermissionFlags::None;
AddWhitelistPermissionFlags(permission_flags, addrConnect, vWhitelistedRangeOutgoing);
InitializePermissionFlags(permission_flags);

// Add node
NodeId id = GetNewNodeId();
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
Expand All @@ -584,6 +588,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
conn_type,
/*inbound_onion=*/false,
CNodeOptions{
.permission_flags = permission_flags,
.i2p_sam_session = std::move(i2p_transient_session),
.recv_flood_size = nReceiveFloodSize,
});
Expand All @@ -606,8 +611,8 @@ void CNode::CloseSocketDisconnect()
m_i2p_sam_session.reset();
}

void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const {
for (const auto& subnet : vWhitelistedRange) {
void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr, const std::vector<NetWhitelistPermissions>& ranges) const {
for (const auto& subnet : ranges) {
if (subnet.m_subnet.Match(addr)) NetPermissions::AddFlag(flags, subnet.m_flags);
}
}
Expand Down Expand Up @@ -986,7 +991,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
int nInbound = 0;
int nMaxInbound = nMaxConnections - m_max_outbound;

AddWhitelistPermissionFlags(permission_flags, addr);
AddWhitelistPermissionFlags(permission_flags, addr, vWhitelistedRange);
InitializePermissionFlags(permission_flags);

{
Expand Down Expand Up @@ -2029,9 +2034,9 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
return;

CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, conn_type);

if (!pnode)
return;

if (grantOutbound)
grantOutbound->MoveTo(pnode->grantOutbound);

Expand Down
6 changes: 5 additions & 1 deletion src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ class CConnman
int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT;
std::vector<std::string> vSeedNodes;
std::vector<NetWhitelistPermissions> vWhitelistedRange;
std::vector<NetWhitelistPermissions> vWhitelistedRangeOutgoing;
std::vector<NetWhitebindPermissions> vWhiteBinds;
std::vector<CService> vBinds;
std::vector<CService> onion_binds;
Expand Down Expand Up @@ -746,6 +747,7 @@ class CConnman
nMaxOutboundLimit = connOptions.nMaxOutboundLimit;
}
vWhitelistedRange = connOptions.vWhitelistedRange;
vWhitelistedRangeOutgoing = connOptions.vWhitelistedRangeOutgoing;
{
LOCK(m_added_nodes_mutex);
m_added_nodes = connOptions.m_added_nodes;
Expand Down Expand Up @@ -983,7 +985,7 @@ class CConnman

bool AttemptToEvictConnection();
CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex);
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const;
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr, const std::vector<NetWhitelistPermissions>& ranges) const;

void DeleteNode(CNode* pnode);

Expand Down Expand Up @@ -1026,6 +1028,8 @@ class CConnman
// Whitelisted ranges. Any node connecting from these is automatically
// whitelisted (as well as those connecting to whitelisted binds).
std::vector<NetWhitelistPermissions> vWhitelistedRange;
// Whitelisted ranges for outgoing connections.
std::vector<NetWhitelistPermissions> vWhitelistedRangeOutgoing;

unsigned int nSendBufferMaxSize{0};
unsigned int nReceiveFloodSize{0};
Expand Down
22 changes: 18 additions & 4 deletions src/net_permissions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ const std::vector<std::string> NET_PERMISSIONS_DOC{
namespace {

// Parse the following format: "perm1,perm2@xxxxxx"
bool TryParsePermissionFlags(const std::string& str, NetPermissionFlags& output, size_t& readen, bilingual_str& error)
static bool TryParsePermissionFlags(const std::string& str, NetPermissionFlags& output, ConnectionDirection* output_connection_direction, size_t& readen, bilingual_str& error)
{
NetPermissionFlags flags = NetPermissionFlags::None;
ConnectionDirection connection_direction = ConnectionDirection::None;
const auto atSeparator = str.find('@');

// if '@' is not found (ie, "xxxxx"), the caller should apply implicit permissions
Expand Down Expand Up @@ -54,6 +55,15 @@ bool TryParsePermissionFlags(const std::string& str, NetPermissionFlags& output,
else if (permission == "all") NetPermissions::AddFlag(flags, NetPermissionFlags::All);
else if (permission == "relay") NetPermissions::AddFlag(flags, NetPermissionFlags::Relay);
else if (permission == "addr") NetPermissions::AddFlag(flags, NetPermissionFlags::Addr);
else if (permission == "in") connection_direction |= ConnectionDirection::In;
else if (permission == "out") {
if (output_connection_direction == nullptr) {
// Only NetWhitebindPermissions() should pass a nullptr.
error = _("whitebind may only be used for incoming connections (\"out\" was passed)");
return false;
}
connection_direction |= ConnectionDirection::Out;
}
else if (permission.length() == 0); // Allow empty entries
else {
error = strprintf(_("Invalid P2P permission: '%s'"), permission);
Expand All @@ -63,7 +73,11 @@ bool TryParsePermissionFlags(const std::string& str, NetPermissionFlags& output,
readen++;
}

// By default, whitelist only applies to incoming connections
if (connection_direction == ConnectionDirection::None) connection_direction = ConnectionDirection::In;

output = flags;
if (output_connection_direction) *output_connection_direction = connection_direction;
error = Untranslated("");
return true;
}
Expand All @@ -88,7 +102,7 @@ bool NetWhitebindPermissions::TryParse(const std::string& str, NetWhitebindPermi
{
NetPermissionFlags flags;
size_t offset;
if (!TryParsePermissionFlags(str, flags, offset, error)) return false;
if (!TryParsePermissionFlags(str, flags, /*output_connection_direction=*/nullptr, offset, error)) return false;

const std::string strBind = str.substr(offset);
CService addrBind;
Expand All @@ -107,11 +121,11 @@ bool NetWhitebindPermissions::TryParse(const std::string& str, NetWhitebindPermi
return true;
}

bool NetWhitelistPermissions::TryParse(const std::string& str, NetWhitelistPermissions& output, bilingual_str& error)
bool NetWhitelistPermissions::TryParse(const std::string& str, NetWhitelistPermissions& output, ConnectionDirection& output_connection_direction, bilingual_str& error)
{
NetPermissionFlags flags;
size_t offset;
if (!TryParsePermissionFlags(str, flags, offset, error)) return false;
if (!TryParsePermissionFlags(str, flags, &output_connection_direction, offset, error)) return false;

const std::string net = str.substr(offset);
CSubNet subnet;
Expand Down
3 changes: 2 additions & 1 deletion src/net_permissions.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <netaddress.h>
#include <netbase.h>

#include <string>
#include <type_traits>
Expand Down Expand Up @@ -88,7 +89,7 @@ class NetWhitebindPermissions : public NetPermissions
class NetWhitelistPermissions : public NetPermissions
{
public:
static bool TryParse(const std::string& str, NetWhitelistPermissions& output, bilingual_str& error);
static bool TryParse(const std::string& str, NetWhitelistPermissions& output, ConnectionDirection& output_connection_direction, bilingual_str& error);
CSubNet m_subnet;
};

Expand Down
4 changes: 3 additions & 1 deletion src/test/fuzz/net_permissions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <net_permissions.h>
#include <netbase.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
Expand Down Expand Up @@ -31,8 +32,9 @@ FUZZ_TARGET(net_permissions)
}

NetWhitelistPermissions net_whitelist_permissions;
ConnectionDirection connection_direction;
bilingual_str error_net_whitelist_permissions;
if (NetWhitelistPermissions::TryParse(s, net_whitelist_permissions, error_net_whitelist_permissions)) {
if (NetWhitelistPermissions::TryParse(s, net_whitelist_permissions, connection_direction, error_net_whitelist_permissions)) {
(void)NetPermissions::ToStrings(net_whitelist_permissions.m_flags);
(void)NetPermissions::AddFlag(net_whitelist_permissions.m_flags, net_permission_flags);
assert(NetPermissions::HasFlag(net_whitelist_permissions.m_flags, net_permission_flags));
Expand Down
18 changes: 14 additions & 4 deletions src/test/netbase_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ BOOST_AUTO_TEST_CASE(netpermissions_test)
bilingual_str error;
NetWhitebindPermissions whitebindPermissions;
NetWhitelistPermissions whitelistPermissions;
ConnectionDirection connection_direction;

// Detect invalid white bind
BOOST_CHECK(!NetWhitebindPermissions::TryParse("", whitebindPermissions, error));
Expand Down Expand Up @@ -445,24 +446,33 @@ BOOST_AUTO_TEST_CASE(netpermissions_test)
BOOST_CHECK(NetWhitebindPermissions::TryParse(",,@1.2.3.4:32", whitebindPermissions, error));
BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, NetPermissionFlags::None);

BOOST_CHECK(!NetWhitebindPermissions::TryParse("out,forcerelay@1.2.3.4:32", whitebindPermissions, error));
BOOST_CHECK(error.original.find("whitebind may only be used for incoming connections (\"out\" was passed)") != std::string::npos);

// Detect invalid flag
BOOST_CHECK(!NetWhitebindPermissions::TryParse("bloom,forcerelay,oopsie@1.2.3.4:32", whitebindPermissions, error));
BOOST_CHECK(error.original.find("Invalid P2P permission") != std::string::npos);

// Check netmask error
BOOST_CHECK(!NetWhitelistPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitelistPermissions, error));
BOOST_CHECK(!NetWhitelistPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitelistPermissions, connection_direction, error));
BOOST_CHECK(error.original.find("Invalid netmask specified in -whitelist") != std::string::npos);

// Happy path for whitelist parsing
BOOST_CHECK(NetWhitelistPermissions::TryParse("noban@1.2.3.4", whitelistPermissions, error));
BOOST_CHECK(NetWhitelistPermissions::TryParse("noban@1.2.3.4", whitelistPermissions, connection_direction, error));
BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, NetPermissionFlags::NoBan);
BOOST_CHECK(NetPermissions::HasFlag(whitelistPermissions.m_flags, NetPermissionFlags::NoBan));

BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay@1.2.3.4/32", whitelistPermissions, error));
BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay@1.2.3.4/32", whitelistPermissions, connection_direction, error));
BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, NetPermissionFlags::BloomFilter | NetPermissionFlags::ForceRelay | NetPermissionFlags::NoBan | NetPermissionFlags::Relay);
BOOST_CHECK(error.empty());
BOOST_CHECK_EQUAL(whitelistPermissions.m_subnet.ToString(), "1.2.3.4/32");
BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay,mempool@1.2.3.4/32", whitelistPermissions, error));
BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay,mempool@1.2.3.4/32", whitelistPermissions, connection_direction, error));
BOOST_CHECK(NetWhitelistPermissions::TryParse("in,relay@1.2.3.4", whitelistPermissions, connection_direction, error));
BOOST_CHECK_EQUAL(connection_direction, ConnectionDirection::In);
BOOST_CHECK(NetWhitelistPermissions::TryParse("out,bloom@1.2.3.4", whitelistPermissions, connection_direction, error));
BOOST_CHECK_EQUAL(connection_direction, ConnectionDirection::Out);
BOOST_CHECK(NetWhitelistPermissions::TryParse("in,out,bloom@1.2.3.4", whitelistPermissions, connection_direction, error));
BOOST_CHECK_EQUAL(connection_direction, ConnectionDirection::Both);

const auto strings = NetPermissions::ToStrings(NetPermissionFlags::All);
BOOST_CHECK_EQUAL(strings.size(), 8U);
Expand Down

0 comments on commit 7ca2624

Please sign in to comment.