init, net: Implement usage of binary-embedded asmap data
What changed, and why it matters
This commit adds an optional feature that lets Bitcoin Core ship its IP-to-ASN mapping data built into the program binary, so users can enable it with a simple `-asmap` flag instead of downloading and pointing to a separate file. It is a feature addition, not a fix for an active security flaw. The change could have minor security implications if the embedded data were malformed or tampered with, but the code validates the data before use and fails startup if validation fails.
Review the generated `ip_asn.dat.h` header and `CheckStandardAsmap` implementation to confirm the embedded data is immutable, correctly sized, and robustly parsed. Ensure build processes that set `ENABLE_EMBEDDED_ASMAP` reproducibly generate the same embedded data. No urgent patch is indicated by this commit alone.
Security signals we found
New startup path parses externally-supplied or embedded asmap data; malformed data aborts startup
Embedded data is validated with `CheckStandardAsmap` before use
Build-time flag `ENABLE_EMBEDDED_ASMAP` gates the embedded data path
No memory-safety primitives (bounds, span lifetime) are visible in the diff; relies on generated header and existing helpers
Change removes the requirement that `-asmap` must include a file path, widening the CLI surface
Evidence from the diff
The patch modifies src/init.cpp to support -asmap without a path argument. When compiled with ENABLE_EMBEDDED_ASMAP, it uses a binary-embedded node::data::ip_asn array and initializes NetGroupManager via WithEmbeddedAsmap. When not compiled with that flag, -asmap without a path now errors with ‘Embedded asmap data not available’. The previous behavior required -asmap=<file> and rejected bare -asmap or -asmap=. The functional test feature_asmap.py is updated to test both embedded-success and embedded-unavailable paths.
Changed components
src/init.cpptest/functional/feature_asmap.pyNetGroupManager / asmap initializationInspect captured patch +58 / −26
diff --git a/src/init.cpp b/src/init.cpp
index a44cdf80..6cbeea3c 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -119,6 +119,10 @@
#include <zmq/zmqrpc.h>
#endif
+#ifdef ENABLE_EMBEDDED_ASMAP
+#include <node/data/ip_asn.dat.h>
+#endif
+
using common::AmountErrMsg;
using common::InvalidPortErrMsg;
using common::ResolveErrMsg;
@@ -1560,29 +1564,50 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
ApplyArgsManOptions(args, peerman_opts);
{
- // Read asmap file if configured and initialize
+ // Read asmap file if configured or embedded asmap data and initialize
// Netgroupman with or without it
assert(!node.netgroupman);
if (args.IsArgSet("-asmap") && !args.IsArgNegated("-asmap")) {
- fs::path asmap_path = args.GetPathArg("-asmap");
- if (asmap_path.empty()) {
- InitError(_("-asmap requires a file path. Use -asmap=<file>."));
- return false;
- }
- if (!asmap_path.is_absolute()) {
- asmap_path = args.GetDataDirNet() / asmap_path;
- }
- if (!fs::exists(asmap_path)) {
- InitError(strprintf(_("Could not find asmap file %s"), fs::quoted(fs::PathToString(asmap_path))));
- return false;
- }
- std::vector<std::byte> asmap{DecodeAsmap(asmap_path)};
- if (asmap.size() == 0) {
- InitError(strprintf(_("Could not parse asmap file %s"), fs::quoted(fs::PathToString(asmap_path))));
- return false;
+ uint256 asmap_version{};
+ if (!args.GetBoolArg("-asmap", false)) {
+ fs::path asmap_path = args.GetPathArg("-asmap");
+ if (!asmap_path.is_absolute()) {
+ asmap_path = args.GetDataDirNet() / asmap_path;
+ }
+
+ // If a specific path was passed with the asmap argument check if
+ // the file actually exists in that location
+ if (!fs::exists(asmap_path)) {
+ InitError(strprintf(_("Could not find asmap file %s"), fs::quoted(fs::PathToString(asmap_path))));
+ return false;
+ }
+
+ // If a file exists at the path, try to read the file
+ std::vector<std::byte> asmap{DecodeAsmap(asmap_path)};
+ if (asmap.empty()) {
+ InitError(strprintf(_("Could not parse asmap file %s"), fs::quoted(fs::PathToString(asmap_path))));
+ return false;
+ }
+ asmap_version = AsmapVersion(asmap);
+ node.netgroupman = std::make_unique<NetGroupManager>(NetGroupManager::WithLoadedAsmap(std::move(asmap)));
+ } else {
+ #ifdef ENABLE_EMBEDDED_ASMAP
+ // Use the embedded asmap data
+ std::span<const std::byte> asmap{node::data::ip_asn};
+ if (asmap.empty() || !CheckStandardAsmap(asmap)) {
+ InitError(strprintf(_("Could not read embedded asmap data")));
+ return false;
+ }
+ node.netgroupman = std::make_unique<NetGroupManager>(NetGroupManager::WithEmbeddedAsmap(asmap));
+ asmap_version = AsmapVersion(asmap);
+ LogInfo("Opened asmap data (%zu bytes) from embedded byte array\n", asmap.size());
+ #else
+ // If there is no embedded data, fail and report it since
+ // the user tried to use it
+ InitError(strprintf(_("Embedded asmap data not available")));
+ return false;
+ #endif
}
- const uint256 asmap_version = AsmapVersion(asmap);
- node.netgroupman = std::make_unique<NetGroupManager>(NetGroupManager::WithLoadedAsmap(std::move(asmap)));
LogInfo("Using asmap version %s for IP bucketing", asmap_version.ToString());
} else {
node.netgroupman = std::make_unique<NetGroupManager>(NetGroupManager::NoAsmap());
diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py
index 8ad59c82..310788f2 100755
--- a/test/functional/feature_asmap.py
+++ b/test/functional/feature_asmap.py
@@ -66,12 +66,19 @@ class AsmapTest(BitcoinTestFramework):
self.start_node(0, [f'-asmap={name}'])
os.remove(filename)
- def test_unspecified_asmap(self):
- msg = "Error: -asmap requires a file path. Use -asmap=<file>."
- for arg in ['-asmap', '-asmap=']:
- self.log.info(f'Test bitcoind {arg} (and no filename specified)')
- self.stop_node(0)
- self.node.assert_start_raises_init_error(extra_args=[arg], expected_msg=msg)
+ def test_embedded_asmap(self):
+ if self.is_embedded_asmap_compiled():
+ self.log.info('Test bitcoind -asmap (using embedded map data)')
+ for arg in ['-asmap', '-asmap=1']:
+ self.stop_node(0)
+ with self.node.assert_debug_log(["Opened asmap data", "from embedded byte array"]):
+ self.start_node(0, [arg])
+ else:
+ self.log.info('Test bitcoind -asmap (compiled without embedded map data)')
+ for arg in ['-asmap', '-asmap=1']:
+ self.stop_node(0)
+ msg = "Error: Embedded asmap data not available"
+ self.node.assert_start_raises_init_error(extra_args=[arg], expected_msg=msg)
def test_asmap_interaction_with_addrman_containing_entries(self):
self.log.info("Test bitcoind -asmap restart with addrman containing new and tried entries")
@@ -127,7 +134,7 @@ class AsmapTest(BitcoinTestFramework):
self.test_noasmap_arg()
self.test_asmap_with_absolute_path()
self.test_asmap_with_relative_path()
- self.test_unspecified_asmap()
+ self.test_embedded_asmap()
self.test_asmap_interaction_with_addrman_containing_entries()
self.test_asmap_with_missing_file()
self.test_empty_asmap()
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.