Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/release-notes-27114.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Additional flags "in" and "out" have been added to `-whitelist` to control whether
permissions apply to incoming connections and/or manual (default: incoming only).
24 changes: 17 additions & 7 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,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. Disables automatic broadcast and rebroadcast of transactions, unless the source peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#if HAVE_SYSTEM
argsman.AddArg("-chainlocknotify=<cmd>", "Execute command when the best chainlock changes (%s in cmd is replaced by chainlocked block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
Expand Down Expand Up @@ -722,9 +722,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 manual (default: incoming only). "
"Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);

g_wallet_init_interface.AddWalletOptions(argsman);

Expand Down Expand Up @@ -859,8 +861,8 @@ void SetupServerArgs(ArgsManager& argsman)
OptionsCategory::NODE_RELAY);
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kB) 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("-blockmaxsize=<n>", strprintf("Set maximum block size in bytes (default: %d)", DEFAULT_BLOCK_MAX_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
argsman.AddArg("-blockmintxfee=<amt>", strprintf("Set lowest fee rate (in %s/kB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
Expand Down Expand Up @@ -2638,6 +2640,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// wallets may mix with CoinJoin, which connects to the mixing masternode
connOptions.m_masternode_connections = node.active_ctx != nullptr || quorums_watch ||
node.wallet_loader != nullptr;
connOptions.whitelist_forcerelay = args.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY);
connOptions.whitelist_relay = args.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY);

// Port to bind to if `-bind=addr` is provided without a `:port` suffix.
const uint16_t default_bind_port =
Expand Down Expand Up @@ -2725,9 +2729,15 @@ 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);
connOptions.vWhitelistedRange.push_back(subnet);
if (!NetWhitelistPermissions::TryParse(net, subnet, connection_direction, error)) return InitError(error);
if (connection_direction & ConnectionDirection::In) {
connOptions.vWhitelistedRangeIncoming.push_back(subnet);
}
if (connection_direction & ConnectionDirection::Out) {
connOptions.vWhitelistedRangeOutgoing.push_back(subnet);
}
}

connOptions.vSeedNodes = args.GetArgs("-seednode");
Expand Down
43 changes: 24 additions & 19 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
return nullptr;
}

NetPermissionFlags permission_flags = NetPermissionFlags::None;
std::vector<NetWhitelistPermissions> whitelist_permissions = conn_type == ConnectionType::MANUAL ? vWhitelistedRangeOutgoing : std::vector<NetWhitelistPermissions>{};
AddWhitelistPermissionFlags(permission_flags, addrConnect, whitelist_permissions);
Comment thread
PastaPastaPasta marked this conversation as resolved.
Resolved

// Add node
NodeId id = GetNewNodeId();
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
Expand All @@ -505,6 +509,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,
.use_v2transport = use_v2transport,
Expand Down Expand Up @@ -547,9 +552,18 @@ void CNode::CloseSocketDisconnect(CConnman* connman)
::g_stats_client->inc("peers.disconnect", 1.0f);
}

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);
void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, std::optional<CNetAddr> addr, const std::vector<NetWhitelistPermissions>& ranges) const {
for (const auto& subnet : ranges) {
if (addr.has_value() && subnet.m_subnet.Match(addr.value())) {
NetPermissions::AddFlag(flags, subnet.m_flags);
}
}
if (NetPermissions::HasFlag(flags, NetPermissionFlags::Implicit)) {
NetPermissions::ClearFlag(flags, NetPermissionFlags::Implicit);
if (whitelist_forcerelay) NetPermissions::AddFlag(flags, NetPermissionFlags::ForceRelay);
if (whitelist_relay) NetPermissions::AddFlag(flags, NetPermissionFlags::Relay);
NetPermissions::AddFlag(flags, NetPermissionFlags::Mempool);
NetPermissions::AddFlag(flags, NetPermissionFlags::NoBan);
}
}

Expand Down Expand Up @@ -1888,14 +1902,11 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
int nVerifiedInboundMasternodes = 0;
int nMaxInbound = nMaxConnections - m_max_outbound;

AddWhitelistPermissionFlags(permission_flags, addr);
if (NetPermissions::HasFlag(permission_flags, NetPermissionFlags::Implicit)) {
NetPermissions::ClearFlag(permission_flags, NetPermissionFlags::Implicit);
if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) NetPermissions::AddFlag(permission_flags, NetPermissionFlags::ForceRelay);
if (gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) NetPermissions::AddFlag(permission_flags, NetPermissionFlags::Relay);
NetPermissions::AddFlag(permission_flags, NetPermissionFlags::Mempool);
NetPermissions::AddFlag(permission_flags, NetPermissionFlags::NoBan);
}
const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

// Tor inbound connections do not reveal the peer's actual network address.
// Therefore do not apply address-based whitelist permissions to them.
AddWhitelistPermissionFlags(permission_flags, inbound_onion ? std::optional<CNetAddr>{} : addr, vWhitelistedRangeIncoming);


{
Expand Down Expand Up @@ -1975,15 +1986,9 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
NodeId id = GetNewNodeId();
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();

ServiceFlags nodeServices = nLocalServices;
if (NetPermissions::HasFlag(permission_flags, NetPermissionFlags::BloomFilter)) {
nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM);
}

const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end();
// The V2Transport transparently falls back to V1 behavior when an incoming V1 connection is
// detected, so use it whenever we signal NODE_P2P_V2.
const bool use_v2transport(nodeServices & NODE_P2P_V2);
const bool use_v2transport(nLocalServices & NODE_P2P_V2);

CNode* pnode = new CNode(id,
std::move(sock),
Expand All @@ -2001,7 +2006,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
.use_v2transport = use_v2transport,
});
pnode->AddRef();
m_msgproc->InitializeNode(*pnode, nodeServices);
m_msgproc->InitializeNode(*pnode, nLocalServices);

{
LOCK(pnode->m_sock_mutex);
Expand Down
33 changes: 24 additions & 9 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ class CScheduler;
struct bilingual_str;
struct NodeEvictionCandidate;

/** Default for -whitelistrelay. */
static const bool DEFAULT_WHITELISTRELAY = true;
/** Default for -whitelistforcerelay. */
static const bool DEFAULT_WHITELISTFORCERELAY = false;

/** Time after which to disconnect, after waiting for a ping response (or inactivity). */
static constexpr std::chrono::minutes TIMEOUT_INTERVAL{20};
/** Time to wait since m_connected before disconnecting a probe node. */
Expand Down Expand Up @@ -1238,7 +1233,8 @@ friend class CNode;
uint64_t nMaxOutboundLimit = 0;
int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT;
std::vector<std::string> vSeedNodes;
std::vector<NetWhitelistPermissions> vWhitelistedRange;
std::vector<NetWhitelistPermissions> vWhitelistedRangeIncoming;
std::vector<NetWhitelistPermissions> vWhitelistedRangeOutgoing;
std::vector<NetWhitebindPermissions> vWhiteBinds;
std::vector<CService> vBinds;
std::vector<CService> onion_binds;
Expand All @@ -1254,6 +1250,8 @@ friend class CNode;
//! Run the thread that opens connections to masternodes; only masternodes, quorum watchers and
//! CoinJoin mixing ever request them.
bool m_masternode_connections = true;
bool whitelist_forcerelay = DEFAULT_WHITELISTFORCERELAY;
bool whitelist_relay = DEFAULT_WHITELISTRELAY;
};

void Init(const Options& connOptions) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_total_bytes_sent_mutex)
Expand All @@ -1279,7 +1277,8 @@ friend class CNode;
LOCK(m_total_bytes_sent_mutex);
nMaxOutboundLimit = connOptions.nMaxOutboundLimit;
}
vWhitelistedRange = connOptions.vWhitelistedRange;
vWhitelistedRangeIncoming = connOptions.vWhitelistedRangeIncoming;
vWhitelistedRangeOutgoing = connOptions.vWhitelistedRangeOutgoing;
{
LOCK(m_added_nodes_mutex);
// Attempt v2 connection if we support v2 - we'll reconnect with v1 if our
Expand All @@ -1293,6 +1292,8 @@ friend class CNode;
m_onion_binds = connOptions.onion_binds;
m_active_masternode = connOptions.m_active_masternode;
m_masternode_connections = connOptions.m_masternode_connections;
whitelist_forcerelay = connOptions.whitelist_forcerelay;
whitelist_relay = connOptions.whitelist_relay;
}

CConnman(uint64_t seed0, uint64_t seed1, AddrMan& addrman, const NetGroupManager& netgroupman,
Expand Down Expand Up @@ -1749,7 +1750,7 @@ friend class CNode;
std::vector<NodeEvictionCandidate> GetEvictionCandidates() const EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex);
bool AttemptToEvictConnection() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex);
CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type, bool use_v2transport) EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !m_unused_i2p_sessions_mutex);
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const;
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, std::optional<CNetAddr> addr, const std::vector<NetWhitelistPermissions>& ranges) const;

void DeleteNode(CNode* pnode);

Expand Down Expand Up @@ -1807,7 +1808,9 @@ friend class CNode;

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

unsigned int nSendBufferMaxSize{0};
unsigned int nReceiveFloodSize{0};
Expand Down Expand Up @@ -1990,6 +1993,18 @@ friend class CNode;
*/
std::vector<CService> m_onion_binds;

/**
* flag for adding 'forcerelay' permission to whitelisted inbound
* and manual peers with default permissions.
*/
bool whitelist_forcerelay;

/**
* flag for adding 'relay' permission to whitelisted inbound
* and manual peers with default permissions.
*/
bool whitelist_relay;

/**
* Mutex protecting m_i2p_sam_sessions.
*/
Expand Down
28 changes: 24 additions & 4 deletions src/net_permissions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,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 @@ -52,6 +53,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 @@ -61,7 +71,16 @@ 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;
} else if (flags == NetPermissionFlags::None) {
error = strprintf(_("Only direction was set, no permissions: '%s'"), str);
return false;
}

output = flags;
if (output_connection_direction) *output_connection_direction = connection_direction;
error = Untranslated("");
return true;
}
Expand All @@ -85,7 +104,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);
const std::optional<CService> addrBind{Lookup(strBind, 0, false)};
Expand All @@ -104,11 +123,12 @@ 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;
// Only NetWhitebindPermissions should pass a nullptr for output_connection_direction.
if (!TryParsePermissionFlags(str, flags, &output_connection_direction, offset, error)) return false;

const std::string net = str.substr(offset);
const CSubNet subnet{LookupSubNet(net)};
Expand Down
Loading
Loading