diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 43b611011d06..c0e1c53809ad 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -227,5 +227,8 @@ new_features: :ref:`happy_eyeballs_config `. A default configuration will be used if not provided. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.use_config_in_happy_eyeballs`` to false. +- area: geoip + change: | + Added ``envoy.reloadable_features.mmdb_files_reload_enabled`` runtime flag that enables reload of mmdb files by default. deprecated: diff --git a/docs/root/configuration/http/http_filters/geoip_filter.rst b/docs/root/configuration/http/http_filters/geoip_filter.rst index 0c6c18d7ac15..ed232749edd7 100644 --- a/docs/root/configuration/http/http_filters/geoip_filter.rst +++ b/docs/root/configuration/http/http_filters/geoip_filter.rst @@ -76,5 +76,7 @@ per geolocation database type (rooted at ``.maxmind.``). Database t ``.total``, Counter, Total number of lookups performed for a given geolocation database file. ``.hit``, Counter, Total number of successful lookups (with non empty lookup result) performed for a given geolocation database file. ``.lookup_error``, Counter, Total number of errors that occured during lookups for a given geolocation database file. + ``.db_reload_success``, Counter, Total number of times when the geolocation database file was reloaded successfully. + ``.db_reload_error``, Counter, Total number of times when the geolocation database file failed to reload. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index cff3db99a0bb..7089ef8daa47 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -66,6 +66,7 @@ RUNTIME_GUARD(envoy_reloadable_features_internal_authority_header_validator); RUNTIME_GUARD(envoy_reloadable_features_jwt_authn_remove_jwt_from_query_params); RUNTIME_GUARD(envoy_reloadable_features_jwt_authn_validate_uri); RUNTIME_GUARD(envoy_reloadable_features_lua_flow_control_while_http_call); +RUNTIME_GUARD(envoy_reloadable_features_mmdb_files_reload_enabled); RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name); RUNTIME_GUARD(envoy_reloadable_features_no_timer_based_rate_limit_token_bucket); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); diff --git a/source/extensions/geoip_providers/maxmind/BUILD b/source/extensions/geoip_providers/maxmind/BUILD index d0a9fc323375..016d6ebad64f 100644 --- a/source/extensions/geoip_providers/maxmind/BUILD +++ b/source/extensions/geoip_providers/maxmind/BUILD @@ -43,6 +43,7 @@ envoy_cc_library( deps = [ "//bazel/foreign_cc:maxmind_linux_darwin", "//envoy/geoip:geoip_provider_driver_interface", + "//source/common/common:thread_synchronizer_lib", "@envoy_api//envoy/extensions/geoip_providers/maxmind/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/geoip_providers/maxmind/config.cc b/source/extensions/geoip_providers/maxmind/config.cc index d75a56b3e659..b2534b6e97be 100644 --- a/source/extensions/geoip_providers/maxmind/config.cc +++ b/source/extensions/geoip_providers/maxmind/config.cc @@ -35,7 +35,9 @@ class DriverSingleton : public Envoy::Singleton::Instance { } else { const auto& provider_config = std::make_shared(proto_config, stat_prefix, context.scope()); - driver = std::make_shared(singleton, provider_config); + driver = std::make_shared( + context.serverFactoryContext().mainThreadDispatcher(), + context.serverFactoryContext().api(), singleton, provider_config); drivers_[key] = driver; } return driver; diff --git a/source/extensions/geoip_providers/maxmind/geoip_provider.cc b/source/extensions/geoip_providers/maxmind/geoip_provider.cc index 5af87388c318..e56d9250c75e 100644 --- a/source/extensions/geoip_providers/maxmind/geoip_provider.cc +++ b/source/extensions/geoip_providers/maxmind/geoip_provider.cc @@ -2,6 +2,7 @@ #include "source/common/common/assert.h" #include "source/common/protobuf/protobuf.h" +#include "source/common/runtime/runtime_features.h" namespace Envoy { namespace Extensions { @@ -16,6 +17,10 @@ static constexpr const char* MMDB_ASN_LOOKUP_ARGS[] = {"autonomous_system_number static constexpr const char* MMDB_ANON_LOOKUP_ARGS[] = {"is_anonymous", "is_anonymous_vpn", "is_hosting_provider", "is_tor_exit_node", "is_public_proxy"}; + +static constexpr absl::string_view CITY_DB_TYPE = "city_db"; +static constexpr absl::string_view ISP_DB_TYPE = "isp_db"; +static constexpr absl::string_view ANON_DB_TYPE = "anon_db"; } // namespace GeoipProviderConfig::GeoipProviderConfig( @@ -60,20 +65,22 @@ GeoipProviderConfig::GeoipProviderConfig( "city_db_path, isp_db_path or anon_db_path"); } if (city_db_path_) { - registerGeoDbStats("city_db"); + registerGeoDbStats(CITY_DB_TYPE); } if (isp_db_path_) { - registerGeoDbStats("isp_db"); + registerGeoDbStats(ISP_DB_TYPE); } if (anon_db_path_) { - registerGeoDbStats("anon_db"); + registerGeoDbStats(ANON_DB_TYPE); } }; -void GeoipProviderConfig::registerGeoDbStats(const std::string& db_type) { +void GeoipProviderConfig::registerGeoDbStats(const absl::string_view& db_type) { stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".total")); stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".hit")); stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".lookup_error")); + stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".db_reload_error")); + stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".db_reload_success")); } bool GeoipProviderConfig::isLookupEnabledForHeader(const absl::optional& header) { @@ -84,30 +91,57 @@ void GeoipProviderConfig::incCounter(Stats::StatName name) { stats_scope_->counterFromStatName(name).inc(); } +GeoipProvider::GeoipProvider(Event::Dispatcher& dispatcher, Api::Api& api, + Singleton::InstanceSharedPtr owner, + GeoipProviderConfigSharedPtr config) + : config_(config), owner_(owner) { + city_db_ = + config_->cityDbPath() ? initMaxmindDb(config_->cityDbPath().value(), CITY_DB_TYPE) : nullptr; + isp_db_ = + config_->ispDbPath() ? initMaxmindDb(config_->ispDbPath().value(), ISP_DB_TYPE) : nullptr; + anon_db_ = + config_->anonDbPath() ? initMaxmindDb(config_->anonDbPath().value(), ANON_DB_TYPE) : nullptr; + mmdb_reload_dispatcher_ = api.allocateDispatcher("mmdb_reload_routine"); + mmdb_watcher_ = dispatcher.createFilesystemWatcher(); + mmdb_reload_thread_ = api.threadFactory().createThread( + [this]() -> void { + ENVOY_LOG_MISC(debug, "Started mmdb_reload_routine"); + if (config_->cityDbPath() && + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.mmdb_files_reload_enabled")) { + THROW_IF_NOT_OK(mmdb_watcher_->addWatch( + config_->cityDbPath().value(), Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { + return onMaxmindDbUpdate(config_->cityDbPath().value(), CITY_DB_TYPE); + })); + } + if (config_->ispDbPath() && + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.mmdb_files_reload_enabled")) { + THROW_IF_NOT_OK(mmdb_watcher_->addWatch( + config_->ispDbPath().value(), Filesystem::Watcher::Events::MovedTo, [this](uint32_t) { + return onMaxmindDbUpdate(config_->ispDbPath().value(), ISP_DB_TYPE); + })); + } + if (config_->anonDbPath() && + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.mmdb_files_reload_enabled")) { + THROW_IF_NOT_OK(mmdb_watcher_->addWatch( + config_->anonDbPath().value(), Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { + return onMaxmindDbUpdate(config_->anonDbPath().value(), ANON_DB_TYPE); + })); + } + mmdb_reload_dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + }, + Thread::Options{std::string("mmdb_reload_routine")}); +}; + GeoipProvider::~GeoipProvider() { ENVOY_LOG(debug, "Shutting down Maxmind geolocation provider"); - if (city_db_) { - MMDB_close(city_db_.get()); - } - if (isp_db_) { - MMDB_close(isp_db_.get()); + if (mmdb_reload_dispatcher_) { + mmdb_reload_dispatcher_->exit(); } - if (anon_db_) { - MMDB_close(anon_db_.get()); - } -} - -MaxmindDbPtr GeoipProvider::initMaxMindDb(const absl::optional& db_path) { - if (db_path) { - MMDB_s maxmind_db; - int result_code = MMDB_open(db_path.value().c_str(), MMDB_MODE_MMAP, &maxmind_db); - RELEASE_ASSERT(MMDB_SUCCESS == result_code, - fmt::format("Unable to open Maxmind database file {}. Error {}", db_path.value(), - std::string(MMDB_strerror(result_code)))); - return std::make_unique(maxmind_db); - } else { - ENVOY_LOG(debug, "Geolocation database path is empty, skipping database creation"); - return nullptr; + if (mmdb_reload_thread_) { + mmdb_reload_thread_->join(); + mmdb_reload_thread_.reset(); } } @@ -127,11 +161,19 @@ void GeoipProvider::lookupInCityDb( if (config_->isLookupEnabledForHeader(config_->cityHeader()) || config_->isLookupEnabledForHeader(config_->regionHeader()) || config_->isLookupEnabledForHeader(config_->countryHeader())) { - ASSERT(city_db_, "Maxmind city database is not initialised for performing lookups"); int mmdb_error; - const uint32_t n_prev_hits = lookup_result.size(); + auto city_db_ptr = getCityDb(); + // Used for testing. + synchronizer_.syncPoint(std::string(CITY_DB_TYPE).append("_lookup_pre_complete")); + if (!city_db_ptr) { + IS_ENVOY_BUG("Maxmind city database must be initialised for performing lookups"); + return; + } + auto city_db = city_db_ptr.get(); MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr( - city_db_.get(), reinterpret_cast(remote_address->sockAddr()), &mmdb_error); + city_db->mmdb(), reinterpret_cast(remote_address->sockAddr()), + &mmdb_error); + const uint32_t n_prev_hits = lookup_result.size(); if (!mmdb_error) { MMDB_entry_data_list_s* entry_data_list; int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list); @@ -152,15 +194,15 @@ void GeoipProvider::lookupInCityDb( MMDB_COUNTRY_LOOKUP_ARGS[1]); } if (lookup_result.size() > n_prev_hits) { - config_->incHit("city_db"); + config_->incHit(CITY_DB_TYPE); } MMDB_free_entry_data_list(entry_data_list); } } else { - config_->incLookupError("city_db"); + config_->incLookupError(CITY_DB_TYPE); } - config_->incTotal("city_db"); + config_->incTotal(CITY_DB_TYPE); } } @@ -168,11 +210,18 @@ void GeoipProvider::lookupInAsnDb( const Network::Address::InstanceConstSharedPtr& remote_address, absl::flat_hash_map& lookup_result) const { if (config_->isLookupEnabledForHeader(config_->asnHeader())) { - RELEASE_ASSERT(isp_db_, "Maxmind asn database is not initialized for performing lookups"); int mmdb_error; - const uint32_t n_prev_hits = lookup_result.size(); + auto isp_db_ptr = getIspDb(); + // Used for testing. + synchronizer_.syncPoint(std::string(ISP_DB_TYPE).append("_lookup_pre_complete")); + if (!isp_db_ptr) { + IS_ENVOY_BUG("Maxmind asn database must be initialised for performing lookups"); + return; + } MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr( - isp_db_.get(), reinterpret_cast(remote_address->sockAddr()), &mmdb_error); + isp_db_ptr->mmdb(), reinterpret_cast(remote_address->sockAddr()), + &mmdb_error); + const uint32_t n_prev_hits = lookup_result.size(); if (!mmdb_error) { MMDB_entry_data_list_s* entry_data_list; int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list); @@ -181,13 +230,13 @@ void GeoipProvider::lookupInAsnDb( MMDB_ASN_LOOKUP_ARGS[0]); MMDB_free_entry_data_list(entry_data_list); if (lookup_result.size() > n_prev_hits) { - config_->incHit("isp_db"); + config_->incHit(ISP_DB_TYPE); } } else { - config_->incLookupError("isp_db"); + config_->incLookupError(ISP_DB_TYPE); } } - config_->incTotal("isp_db"); + config_->incTotal(ISP_DB_TYPE); } } @@ -195,11 +244,19 @@ void GeoipProvider::lookupInAnonDb( const Network::Address::InstanceConstSharedPtr& remote_address, absl::flat_hash_map& lookup_result) const { if (config_->isLookupEnabledForHeader(config_->anonHeader()) || config_->anonVpnHeader()) { - ASSERT(anon_db_, "Maxmind city database is not initialised for performing lookups"); int mmdb_error; - const uint32_t n_prev_hits = lookup_result.size(); + auto anon_db_ptr = getAnonDb(); + // Used for testing. + synchronizer_.syncPoint(std::string(ANON_DB_TYPE).append("_lookup_pre_complete")); + if (!anon_db_ptr) { + IS_ENVOY_BUG("Maxmind anon database must be initialised for performing lookups"); + return; + } + auto anon_db = anon_db_ptr.get(); MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr( - anon_db_.get(), reinterpret_cast(remote_address->sockAddr()), &mmdb_error); + anon_db->mmdb(), reinterpret_cast(remote_address->sockAddr()), + &mmdb_error); + const uint32_t n_prev_hits = lookup_result.size(); if (!mmdb_error) { MMDB_entry_data_list_s* entry_data_list; int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list); @@ -225,15 +282,94 @@ void GeoipProvider::lookupInAnonDb( config_->anonProxyHeader().value(), MMDB_ANON_LOOKUP_ARGS[4]); } if (lookup_result.size() > n_prev_hits) { - config_->incHit("anon_db"); + config_->incHit(ANON_DB_TYPE); } MMDB_free_entry_data_list(entry_data_list); } else { - config_->incLookupError("anon_db"); + config_->incLookupError(ANON_DB_TYPE); } } - config_->incTotal("anon_db"); + config_->incTotal(ANON_DB_TYPE); + } +} + +MaxmindDbSharedPtr GeoipProvider::initMaxmindDb(const std::string& db_path, + const absl::string_view& db_type, bool reload) { + MMDB_s maxmind_db; + int result_code = MMDB_open(db_path.c_str(), MMDB_MODE_MMAP, &maxmind_db); + + if (reload && MMDB_SUCCESS != result_code) { + ENVOY_LOG(error, "Failed to reload Maxmind database {} from file {}. Error {}", db_type, + db_path, std::string(MMDB_strerror(result_code))); + return nullptr; + } else if (MMDB_SUCCESS != result_code) { + // Crash if this is a failure during initial load. + RELEASE_ASSERT(MMDB_SUCCESS == result_code, + fmt::format("Unable to open Maxmind database file {}. Error {}", db_path, + std::string(MMDB_strerror(result_code)))); + return nullptr; + } + + ENVOY_LOG(info, "Succeeded to reload Maxmind database {} from file {}.", db_type, db_path); + return std::make_shared(std::move(maxmind_db)); +} + +absl::Status GeoipProvider::mmdbReload(const MaxmindDbSharedPtr reloaded_db, + const absl::string_view& db_type) { + if (reloaded_db) { + if (db_type == CITY_DB_TYPE) { + updateCityDb(reloaded_db); + config_->incDbReloadSuccess(db_type); + } else if (db_type == ISP_DB_TYPE) { + updateIspDb(reloaded_db); + config_->incDbReloadSuccess(db_type); + } else if (db_type == ANON_DB_TYPE) { + updateAnonDb(reloaded_db); + config_->incDbReloadSuccess(db_type); + } else { + ENVOY_LOG(error, "Unsupported maxmind db type {}", db_type); + return absl::InvalidArgumentError(fmt::format("Unsupported maxmind db type {}", db_type)); + } + } else { + config_->incDbReloadError(db_type); } + return absl::OkStatus(); +} + +MaxmindDbSharedPtr GeoipProvider::getCityDb() const ABSL_LOCKS_EXCLUDED(mmdb_mutex_) { + absl::ReaderMutexLock lock(&mmdb_mutex_); + return city_db_; +} + +void GeoipProvider::updateCityDb(MaxmindDbSharedPtr city_db) ABSL_LOCKS_EXCLUDED(mmdb_mutex_) { + absl::MutexLock lock(&mmdb_mutex_); + city_db_ = city_db; +} + +MaxmindDbSharedPtr GeoipProvider::getIspDb() const ABSL_LOCKS_EXCLUDED(mmdb_mutex_) { + absl::ReaderMutexLock lock(&mmdb_mutex_); + return isp_db_; +} + +void GeoipProvider::updateIspDb(MaxmindDbSharedPtr isp_db) ABSL_LOCKS_EXCLUDED(mmdb_mutex_) { + absl::MutexLock lock(&mmdb_mutex_); + isp_db_ = isp_db; +} + +MaxmindDbSharedPtr GeoipProvider::getAnonDb() const ABSL_LOCKS_EXCLUDED(mmdb_mutex_) { + absl::ReaderMutexLock lock(&mmdb_mutex_); + return anon_db_; +} + +void GeoipProvider::updateAnonDb(MaxmindDbSharedPtr anon_db) ABSL_LOCKS_EXCLUDED(mmdb_mutex_) { + absl::MutexLock lock(&mmdb_mutex_); + anon_db_ = anon_db; +} + +absl::Status GeoipProvider::onMaxmindDbUpdate(const std::string& db_path, + const absl::string_view& db_type) { + MaxmindDbSharedPtr reloaded_db = initMaxmindDb(db_path, db_type, true /* reload */); + return mmdbReload(reloaded_db, db_type); } template diff --git a/source/extensions/geoip_providers/maxmind/geoip_provider.h b/source/extensions/geoip_providers/maxmind/geoip_provider.h index 25f5c11b3a51..2ecf71b6497d 100644 --- a/source/extensions/geoip_providers/maxmind/geoip_provider.h +++ b/source/extensions/geoip_providers/maxmind/geoip_provider.h @@ -5,6 +5,7 @@ #include "envoy/geoip/geoip_provider_driver.h" #include "source/common/common/logger.h" +#include "source/common/common/thread_synchronizer.h" #include "maxminddb.h" @@ -48,7 +49,17 @@ class GeoipProviderConfig { incCounter(stat_name_set_->getBuiltin(absl::StrCat(maxmind_db_type, ".hit"), unknown_hit_)); } - void registerGeoDbStats(const std::string& db_type); + void incDbReloadSuccess(absl::string_view maxmind_db_type) { + incCounter(stat_name_set_->getBuiltin(absl::StrCat(maxmind_db_type, ".db_reload_success"), + unknown_hit_)); + } + + void incDbReloadError(absl::string_view maxmind_db_type) { + incCounter(stat_name_set_->getBuiltin(absl::StrCat(maxmind_db_type, ".db_reload_error"), + unknown_hit_)); + } + + void registerGeoDbStats(const absl::string_view& db_type); Stats::Scope& getStatsScopeForTest() const { return *stats_scope_; } @@ -76,17 +87,25 @@ class GeoipProviderConfig { using GeoipProviderConfigSharedPtr = std::shared_ptr; -using MaxmindDbPtr = std::unique_ptr; +// Wrapper class for MMDB_s type that ensures a proper cleanup of the MMDB_s +// instance resources prior to its destruction. +class MaxmindDb { +public: + MaxmindDb(MMDB_s&& db) : db_(db) {} + ~MaxmindDb() { MMDB_close(&db_); } + const MMDB_s* mmdb() const { return &db_; } + +private: + MMDB_s db_; +}; + +using MaxmindDbSharedPtr = std::shared_ptr; class GeoipProvider : public Envoy::Geolocation::Driver, public Logger::Loggable { public: - GeoipProvider(Singleton::InstanceSharedPtr owner, GeoipProviderConfigSharedPtr config) - : config_(config), owner_(owner) { - city_db_ = initMaxMindDb(config_->cityDbPath()); - isp_db_ = initMaxMindDb(config_->ispDbPath()); - anon_db_ = initMaxMindDb(config_->anonDbPath()); - }; + GeoipProvider(Event::Dispatcher& dispatcher, Api::Api& api, Singleton::InstanceSharedPtr owner, + GeoipProviderConfigSharedPtr config); ~GeoipProvider() override; @@ -97,22 +116,38 @@ class GeoipProvider : public Envoy::Geolocation::Driver, // Allow the unit test to have access to private members. friend class GeoipProviderPeer; GeoipProviderConfigSharedPtr config_; - MaxmindDbPtr city_db_; - MaxmindDbPtr isp_db_; - MaxmindDbPtr anon_db_; - MaxmindDbPtr initMaxMindDb(const absl::optional& db_path); + mutable absl::Mutex mmdb_mutex_; + MaxmindDbSharedPtr city_db_ ABSL_GUARDED_BY(mmdb_mutex_); + MaxmindDbSharedPtr isp_db_ ABSL_GUARDED_BY(mmdb_mutex_); + MaxmindDbSharedPtr anon_db_ ABSL_GUARDED_BY(mmdb_mutex_); + Thread::ThreadPtr mmdb_reload_thread_; + Event::DispatcherPtr mmdb_reload_dispatcher_; + Filesystem::WatcherPtr mmdb_watcher_; + MaxmindDbSharedPtr initMaxmindDb(const std::string& db_path, const absl::string_view& db_type, + bool reload = false); void lookupInCityDb(const Network::Address::InstanceConstSharedPtr& remote_address, absl::flat_hash_map& lookup_result) const; void lookupInAsnDb(const Network::Address::InstanceConstSharedPtr& remote_address, absl::flat_hash_map& lookup_result) const; void lookupInAnonDb(const Network::Address::InstanceConstSharedPtr& remote_address, absl::flat_hash_map& lookup_result) const; + absl::Status onMaxmindDbUpdate(const std::string& db_path, const absl::string_view& db_type); + absl::Status mmdbReload(const MaxmindDbSharedPtr reloaded_db, const absl::string_view& db_type) + ABSL_LOCKS_EXCLUDED(mmdb_mutex_); template void populateGeoLookupResult(MMDB_lookup_result_s& mmdb_lookup_result, absl::flat_hash_map& lookup_result, const std::string& result_key, Params... lookup_params) const; + MaxmindDbSharedPtr getCityDb() const ABSL_LOCKS_EXCLUDED(mmdb_mutex_); + MaxmindDbSharedPtr getIspDb() const ABSL_LOCKS_EXCLUDED(mmdb_mutex_); + MaxmindDbSharedPtr getAnonDb() const ABSL_LOCKS_EXCLUDED(mmdb_mutex_); + void updateCityDb(MaxmindDbSharedPtr city_db) ABSL_LOCKS_EXCLUDED(mmdb_mutex_); + void updateIspDb(MaxmindDbSharedPtr isp_db) ABSL_LOCKS_EXCLUDED(mmdb_mutex_); + void updateAnonDb(MaxmindDbSharedPtr anon_db) ABSL_LOCKS_EXCLUDED(mmdb_mutex_); // A shared_ptr to keep the provider singleton alive as long as any of its providers are in use. const Singleton::InstanceSharedPtr owner_; + // Used for testing only. + mutable Thread::ThreadSynchronizer synchronizer_; }; using GeoipProviderSharedPtr = std::shared_ptr; diff --git a/test/extensions/geoip_providers/maxmind/BUILD b/test/extensions/geoip_providers/maxmind/BUILD index c39b4eb33382..2262723c8acf 100644 --- a/test/extensions/geoip_providers/maxmind/BUILD +++ b/test/extensions/geoip_providers/maxmind/BUILD @@ -50,6 +50,7 @@ envoy_extension_cc_test( "//source/extensions/geoip_providers/maxmind:config", "//source/extensions/geoip_providers/maxmind:provider_impl", "//test/mocks/server:factory_context_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/geoip_providers/maxmind/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/geoip_providers/maxmind/config_test.cc b/test/extensions/geoip_providers/maxmind/config_test.cc index 53b40ec948f3..2e4d72417bf7 100644 --- a/test/extensions/geoip_providers/maxmind/config_test.cc +++ b/test/extensions/geoip_providers/maxmind/config_test.cc @@ -11,7 +11,10 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using ::testing::AllOf; +using testing::AllOf; +using testing::InvokeWithoutArgs; +using testing::Return; +using testing::ReturnRef; namespace Envoy { namespace Extensions { @@ -183,12 +186,35 @@ std::string genGeoDbFilePath(std::string db_name) { "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/" + db_name); } -TEST(MaxmindProviderConfigTest, EmptyProto) { +class MaxmindProviderConfigTest : public testing::Test { +public: + MaxmindProviderConfigTest() : api_(Api::createApiForTest(stats_store_)) { + EXPECT_CALL(context_, serverFactoryContext()) + .WillRepeatedly(ReturnRef(server_factory_context_)); + EXPECT_CALL(server_factory_context_, api()).WillRepeatedly(ReturnRef(*api_)); + EXPECT_CALL(server_factory_context_, mainThreadDispatcher()) + .WillRepeatedly(ReturnRef(dispatcher_)); + EXPECT_CALL(dispatcher_, createFilesystemWatcher_()).WillRepeatedly(InvokeWithoutArgs([&] { + Filesystem::MockWatcher* mock_watcher = new NiceMock(); + EXPECT_CALL(*mock_watcher, addWatch(_, Filesystem::Watcher::Events::MovedTo, _)) + .WillRepeatedly(Return(absl::OkStatus())); + return mock_watcher; + })); + } + + Api::ApiPtr api_; + Stats::IsolatedStoreImpl stats_store_; + Event::MockDispatcher dispatcher_; + NiceMock server_factory_context_; + NiceMock context_; +}; + +TEST_F(MaxmindProviderConfigTest, EmptyProto) { MaxmindProviderFactory factory; EXPECT_TRUE(factory.createEmptyConfigProto() != nullptr); } -TEST(MaxmindProviderConfigTest, ProviderConfigWithCorrectProto) { +TEST_F(MaxmindProviderConfigTest, ProviderConfigWithCorrectProto) { const auto provider_config_yaml = R"EOF( common_provider_config: geo_headers_to_add: @@ -213,11 +239,9 @@ TEST(MaxmindProviderConfigTest, ProviderConfigWithCorrectProto) { auto processed_provider_config_yaml = absl::StrFormat(provider_config_yaml, city_db_path, asn_db_path, anon_db_path); TestUtility::loadFromYaml(processed_provider_config_yaml, provider_config); - NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); MaxmindProviderFactory factory; Geolocation::DriverSharedPtr driver = - factory.createGeoipProviderDriver(provider_config, "maxmind", context); + factory.createGeoipProviderDriver(provider_config, "maxmind", context_); EXPECT_THAT(driver, AllOf(HasCityDbPath(city_db_path), HasIspDbPath(asn_db_path), HasAnonDbPath(anon_db_path), HasCountryHeader("x-geo-country"), HasCityHeader("x-geo-city"), HasRegionHeader("x-geo-region"), @@ -226,7 +250,7 @@ TEST(MaxmindProviderConfigTest, ProviderConfigWithCorrectProto) { HasAnonHostingHeader("x-anon-hosting"))); } -TEST(MaxmindProviderConfigTest, ProviderConfigWithNoDbPaths) { +TEST_F(MaxmindProviderConfigTest, ProviderConfigWithNoDbPaths) { std::string provider_config_yaml = R"EOF( common_provider_config: geo_headers_to_add: @@ -236,7 +260,6 @@ TEST(MaxmindProviderConfigTest, ProviderConfigWithNoDbPaths) { MaxmindProviderConfig provider_config; TestUtility::loadFromYaml(provider_config_yaml, provider_config); NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); MaxmindProviderFactory factory; EXPECT_THROW_WITH_MESSAGE(factory.createGeoipProviderDriver(provider_config, "maxmind", context), Envoy::EnvoyException, @@ -244,7 +267,7 @@ TEST(MaxmindProviderConfigTest, ProviderConfigWithNoDbPaths) { "city_db_path, isp_db_path or anon_db_path"); } -TEST(MaxmindProviderConfigTest, ProviderConfigWithNoGeoHeaders) { +TEST_F(MaxmindProviderConfigTest, ProviderConfigWithNoGeoHeaders) { std::string provider_config_yaml = R"EOF( isp_db_path: "/geoip2/Isp.mmdb" )EOF"; @@ -258,7 +281,7 @@ TEST(MaxmindProviderConfigTest, ProviderConfigWithNoGeoHeaders) { "Proto constraint validation failed.*value is required.*"); } -TEST(MaxmindProviderConfigTest, DbPathFormatValidatedWhenNonEmptyValue) { +TEST_F(MaxmindProviderConfigTest, DbPathFormatValidatedWhenNonEmptyValue) { std::string provider_config_yaml = R"EOF( isp_db_path: "/geoip2/Isp.exe" )EOF"; @@ -273,7 +296,7 @@ TEST(MaxmindProviderConfigTest, DbPathFormatValidatedWhenNonEmptyValue) { "Proto constraint validation failed.*value does not match regex pattern.*"); } -TEST(MaxmindProviderConfigTest, ReusesProviderInstanceForSameProtoConfig) { +TEST_F(MaxmindProviderConfigTest, ReusesProviderInstanceForSameProtoConfig) { const auto provider_config_yaml = R"EOF( common_provider_config: geo_headers_to_add: @@ -295,17 +318,15 @@ TEST(MaxmindProviderConfigTest, ReusesProviderInstanceForSameProtoConfig) { auto processed_provider_config_yaml = absl::StrFormat(provider_config_yaml, city_db_path, asn_db_path, anon_db_path); TestUtility::loadFromYaml(processed_provider_config_yaml, provider_config); - NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()).Times(2); MaxmindProviderFactory factory; Geolocation::DriverSharedPtr driver1 = - factory.createGeoipProviderDriver(provider_config, "maxmind", context); + factory.createGeoipProviderDriver(provider_config, "maxmind", context_); Geolocation::DriverSharedPtr driver2 = - factory.createGeoipProviderDriver(provider_config, "maxmind", context); + factory.createGeoipProviderDriver(provider_config, "maxmind", context_); EXPECT_EQ(driver1.get(), driver2.get()); } -TEST(MaxmindProviderConfigTest, DifferentProviderInstancesForDifferentProtoConfig) { +TEST_F(MaxmindProviderConfigTest, DifferentProviderInstancesForDifferentProtoConfig) { const auto provider_config_yaml1 = R"EOF( common_provider_config: geo_headers_to_add: @@ -343,13 +364,11 @@ TEST(MaxmindProviderConfigTest, DifferentProviderInstancesForDifferentProtoConfi absl::StrFormat(provider_config_yaml2, city_db_path, anon_db_path); TestUtility::loadFromYaml(processed_provider_config_yaml1, provider_config1); TestUtility::loadFromYaml(processed_provider_config_yaml2, provider_config2); - NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()).Times(2); MaxmindProviderFactory factory; Geolocation::DriverSharedPtr driver1 = - factory.createGeoipProviderDriver(provider_config1, "maxmind", context); + factory.createGeoipProviderDriver(provider_config1, "maxmind", context_); Geolocation::DriverSharedPtr driver2 = - factory.createGeoipProviderDriver(provider_config2, "maxmind", context); + factory.createGeoipProviderDriver(provider_config2, "maxmind", context_); EXPECT_NE(driver1.get(), driver2.get()); } diff --git a/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc b/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc index 0b49b72e0d96..1d4eae7932ed 100644 --- a/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc +++ b/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc @@ -9,12 +9,14 @@ #include "test/mocks/server/factory_context.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; +using testing::InvokeWithoutArgs; using testing::NiceMock; using testing::ReturnRef; using testing::SaveArg; @@ -30,25 +32,99 @@ class GeoipProviderPeer { auto provider = std::static_pointer_cast(driver); return provider->config_->getStatsScopeForTest(); } + static Thread::ThreadSynchronizer& synchronizer(const DriverSharedPtr& driver) { + auto provider = std::static_pointer_cast(driver); + return provider->synchronizer_; + } }; -class GeoipProviderTest : public testing::Test { +namespace { +const std::string default_city_db_path = + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb"; + +const std::string default_updated_city_db_path = + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test-Updated.mmdb"; + +const std::string default_city_config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + city: "x-geo-city" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" + )EOF"; + +const std::string default_isp_db_path = + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb"; + +const std::string default_updated_isp_db_path = + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test-Updated.mmdb"; + +const std::string default_isp_config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + asn: "x-geo-asn" + isp_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb" + )EOF"; + +const std::string default_anon_db_path = + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb"; + +const std::string default_updated_anon_db_path = + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test-Updated.mmdb"; + +const std::string default_anon_config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + is_anon: "x-geo-anon" + anon_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb" + )EOF"; +} // namespace + +class GeoipProviderTestBase { public: - GeoipProviderTest() { + GeoipProviderTestBase() : api_(Api::createApiForTest(stats_store_)) { provider_factory_ = dynamic_cast( Registry::FactoryRegistry::getFactory( "envoy.geoip_providers.maxmind")); ASSERT(provider_factory_); } + ~GeoipProviderTestBase() { + absl::WriterMutexLock lock(&mutex_); + on_changed_cbs_.clear(); + }; + void initializeProvider(const std::string& yaml) { EXPECT_CALL(context_, scope()).WillRepeatedly(ReturnRef(*scope_)); + EXPECT_CALL(context_, serverFactoryContext()) + .WillRepeatedly(ReturnRef(server_factory_context_)); + EXPECT_CALL(server_factory_context_, api()).WillRepeatedly(ReturnRef(*api_)); + EXPECT_CALL(dispatcher_, createFilesystemWatcher_()).WillRepeatedly(InvokeWithoutArgs([this] { + Filesystem::MockWatcher* mock_watcher = new NiceMock(); + EXPECT_CALL(*mock_watcher, addWatch(_, Filesystem::Watcher::Events::MovedTo, _)) + .WillRepeatedly( + Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + absl::WriterMutexLock lock(&mutex_); + on_changed_cbs_.emplace_back(std::move(cb)); + return absl::OkStatus(); + })); + return mock_watcher; + })); + EXPECT_CALL(server_factory_context_, mainThreadDispatcher()) + .WillRepeatedly(ReturnRef(dispatcher_)); envoy::extensions::geoip_providers::maxmind::v3::MaxMindConfig config; TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), config); provider_ = provider_factory_->createGeoipProviderDriver(config, "prefix.", context_); } - void expectStats(const std::string& db_type, const uint32_t total_count = 1, + void expectStats(const absl::string_view& db_type, const uint32_t total_count = 1, const uint32_t hit_count = 1, const uint32_t error_count = 0) { auto& provider_scope = GeoipProviderPeer::providerScope(provider_); EXPECT_EQ(provider_scope.counterFromString(absl::StrCat(db_type, ".total")).value(), @@ -58,14 +134,31 @@ class GeoipProviderTest : public testing::Test { error_count); } + void expectReloadStats(const absl::string_view& db_type, const uint32_t reload_success_count = 0, + const uint32_t reload_error_count = 0) { + auto& provider_scope = GeoipProviderPeer::providerScope(provider_); + EXPECT_EQ(provider_scope.counterFromString(absl::StrCat(db_type, ".db_reload_success")).value(), + reload_success_count); + EXPECT_EQ(provider_scope.counterFromString(absl::StrCat(db_type, ".db_reload_error")).value(), + reload_error_count); + } + + Event::MockDispatcher dispatcher_; Stats::IsolatedStoreImpl stats_store_; Stats::ScopeSharedPtr scope_{stats_store_.createScope("")}; + Api::ApiPtr api_; + NiceMock server_factory_context_; NiceMock context_; DriverSharedPtr provider_; MaxmindProviderFactory* provider_factory_; + Event::SimulatedTimeSystem time_system_; absl::flat_hash_map captured_lookup_response_; + absl::Mutex mutex_; + std::vector on_changed_cbs_ ABSL_GUARDED_BY(mutex_); }; +class GeoipProviderTest : public testing::Test, public GeoipProviderTestBase {}; + TEST_F(GeoipProviderTest, ValidConfigCityAndIspDbsSuccessfulLookup) { const std::string config_yaml = R"EOF( common_provider_config: @@ -217,13 +310,7 @@ TEST_F(GeoipProviderTest, ValidConfigAnonProxySuccessfulLookup) { } TEST_F(GeoipProviderTest, ValidConfigEmptyLookupResult) { - const std::string config_yaml = R"EOF( - common_provider_config: - geo_headers_to_add: - is_anon: "x-geo-anon" - anon_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb" - )EOF"; - initializeProvider(config_yaml); + initializeProvider(default_anon_config_yaml); Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddressNoThrow("10.10.10.10"); Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; @@ -236,15 +323,7 @@ TEST_F(GeoipProviderTest, ValidConfigEmptyLookupResult) { } TEST_F(GeoipProviderTest, ValidConfigCityMultipleLookups) { - const std::string config_yaml = R"EOF( - common_provider_config: - geo_headers_to_add: - country: "x-geo-country" - region: "x-geo-region" - city: "x-geo-city" - city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" - )EOF"; - initializeProvider(config_yaml); + initializeProvider(default_city_config_yaml); Network::Address::InstanceConstSharedPtr remote_address1 = Network::Utility::parseInternetAddressNoThrow("78.26.243.166"); Geolocation::LookupRequest lookup_rq1{std::move(remote_address1)}; @@ -265,28 +344,110 @@ TEST_F(GeoipProviderTest, ValidConfigCityMultipleLookups) { expectStats("city_db", 2, 2); } -using GeoipProviderDeathTest = GeoipProviderTest; +TEST_F(GeoipProviderTest, DbReloadedOnMmdbFileUpdate) { + constexpr absl::string_view config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + city: "x-geo-city" + city_db_path: {} + )EOF"; + std::string city_db_path = TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb"); + std::string reloaded_city_db_path = TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test-Updated.mmdb"); + const std::string formatted_config = + fmt::format(config_yaml, TestEnvironment::substitute(city_db_path)); + initializeProvider(formatted_config); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow("78.26.243.166"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(3, captured_lookup_response_.size()); + const auto& city_it = captured_lookup_response_.find("x-geo-city"); + EXPECT_EQ("Boxford", city_it->second); + TestEnvironment::renameFile(city_db_path, city_db_path + "1"); + TestEnvironment::renameFile(reloaded_city_db_path, city_db_path); + { + absl::MutexLock guard(&mutex_); + EXPECT_TRUE(on_changed_cbs_[0](Filesystem::Watcher::Events::MovedTo).ok()); + } + expectReloadStats("city_db", 1, 0); + captured_lookup_response_.clear(); + EXPECT_EQ(0, captured_lookup_response_.size()); + remote_address = Network::Utility::parseInternetAddressNoThrow("78.26.243.166"); + Geolocation::LookupRequest lookup_rq2{std::move(remote_address)}; + testing::MockFunction lookup_cb2; + auto lookup_cb_std2 = lookup_cb2.AsStdFunction(); + EXPECT_CALL(lookup_cb2, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq2), std::move(lookup_cb_std2)); + const auto& city1_it = captured_lookup_response_.find("x-geo-city"); + EXPECT_EQ("BoxfordImaginary", city1_it->second); + // Clean up modifications to mmdb file names. + TestEnvironment::renameFile(city_db_path, reloaded_city_db_path); + TestEnvironment::renameFile(city_db_path + "1", city_db_path); +} -TEST_F(GeoipProviderDeathTest, GeoDbNotSetForConfiguredHeader) { - const std::string config_yaml = R"EOF( +TEST_F(GeoipProviderTest, DbReloadError) { + constexpr absl::string_view config_yaml = R"EOF( common_provider_config: geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" city: "x-geo-city" - asn: "x-geo-asn" - city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" + city_db_path: {} )EOF"; - initializeProvider(config_yaml); + std::string city_db_path = TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb"); + std::string reloaded_invalid_city_db_path = + TestEnvironment::substitute("{{ test_rundir " + "}}/test/extensions/geoip_providers/maxmind/test_data/" + "libmaxminddb-offset-integer-overflow.mmdb"); + const std::string formatted_config = + fmt::format(config_yaml, TestEnvironment::substitute(city_db_path)); + initializeProvider(formatted_config); Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddressNoThrow("78.26.243.166"); Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; testing::MockFunction lookup_cb; auto lookup_cb_std = lookup_cb.AsStdFunction(); EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); - EXPECT_DEATH(provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)), - "assert failure: isp_db_. Details: Maxmind asn database is not initialized for " - "performing lookups"); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(3, captured_lookup_response_.size()); + const auto& city_it = captured_lookup_response_.find("x-geo-city"); + EXPECT_EQ("Boxford", city_it->second); + TestEnvironment::renameFile(city_db_path, city_db_path + "1"); + TestEnvironment::renameFile(reloaded_invalid_city_db_path, city_db_path); + { + absl::MutexLock guard(&mutex_); + EXPECT_TRUE(on_changed_cbs_[0](Filesystem::Watcher::Events::MovedTo).ok()); + } + // On mmdb reload error the old mmdb instance should be used for subsequent lookup requests. + expectReloadStats("city_db", 0, 1); + captured_lookup_response_.clear(); + EXPECT_EQ(0, captured_lookup_response_.size()); + remote_address = Network::Utility::parseInternetAddressNoThrow("78.26.243.166"); + Geolocation::LookupRequest lookup_rq2{std::move(remote_address)}; + testing::MockFunction lookup_cb2; + auto lookup_cb_std2 = lookup_cb2.AsStdFunction(); + EXPECT_CALL(lookup_cb2, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq2), std::move(lookup_cb_std2)); + const auto& city1_it = captured_lookup_response_.find("x-geo-city"); + EXPECT_EQ("Boxford", city1_it->second); + // Clean up modifications to mmdb file names. + TestEnvironment::renameFile(city_db_path, reloaded_invalid_city_db_path); + TestEnvironment::renameFile(city_db_path + "1", city_db_path); } +using GeoipProviderDeathTest = GeoipProviderTest; + TEST_F(GeoipProviderDeathTest, GeoDbPathDoesNotExist) { const std::string config_yaml = R"EOF( common_provider_config: @@ -297,6 +458,228 @@ TEST_F(GeoipProviderDeathTest, GeoDbPathDoesNotExist) { EXPECT_DEATH(initializeProvider(config_yaml), ".*Unable to open Maxmind database file.*"); } +struct GeoipProviderGeoDbNotSetTestCase { + GeoipProviderGeoDbNotSetTestCase() = default; + GeoipProviderGeoDbNotSetTestCase(const std::string& yaml_config, const std::string& db_type) + : yaml_config_(yaml_config), db_type_(db_type) {} + GeoipProviderGeoDbNotSetTestCase(const GeoipProviderGeoDbNotSetTestCase& rhs) = default; + + std::string yaml_config_; + std::string db_type_; +}; + +class GeoipProviderGeoDbNotSetDeathTest + : public ::testing::TestWithParam, + public GeoipProviderTestBase {}; + +TEST_P(GeoipProviderGeoDbNotSetDeathTest, GeoDbNotSetForConfiguredHeader) { + GeoipProviderGeoDbNotSetTestCase test_case = GetParam(); + initializeProvider(test_case.yaml_config_); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow("78.26.243.166"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + EXPECT_ENVOY_BUG( + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)), + fmt::format("envoy bug failure: false. Details: Maxmind {} database must be initialised for " + "performing lookups", + test_case.db_type_)); +} + +struct GeoipProviderGeoDbNotSetTestCase geo_db_not_set_test_cases[] = { + { + R"EOF( + common_provider_config: + geo_headers_to_add: + city: "x-geo-city" + asn: "x-geo-asn" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" + )EOF", + "asn"}, + { + R"EOF( + common_provider_config: + geo_headers_to_add: + city: "x-geo-city" + asn: "x-geo-asn" + isp_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb" + )EOF", + "city"}, + { + R"EOF( + common_provider_config: + geo_headers_to_add: + is_anon: "x-geo-anon" + asn: "x-geo-asn" + isp_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb" + )EOF", + "anon"}, +}; + +INSTANTIATE_TEST_SUITE_P(TestName, GeoipProviderGeoDbNotSetDeathTest, + ::testing::ValuesIn(geo_db_not_set_test_cases)); + +struct MmdbReloadTestCase { + + MmdbReloadTestCase() = default; + MmdbReloadTestCase(const std::string& yaml_config, const std::string& db_type, + const std::string& source_db_file_path, + const std::string& reloaded_db_file_path, + const std::string& expected_header_name, + const std::string& expected_header_value, + const std::string& expected_reloaded_header_value, const std::string& ip) + : yaml_config_(yaml_config), db_type_(db_type), source_db_file_path_(source_db_file_path), + reloaded_db_file_path_(reloaded_db_file_path), expected_header_name_(expected_header_name), + expected_header_value_(expected_header_value), + expected_reloaded_header_value_(expected_reloaded_header_value), ip_(ip) {} + MmdbReloadTestCase(const MmdbReloadTestCase& rhs) = default; + + std::string yaml_config_; + std::string db_type_; + std::string source_db_file_path_; + std::string reloaded_db_file_path_; + std::string expected_header_name_; + std::string expected_header_value_; + std::string expected_reloaded_header_value_; + std::string ip_; +}; + +class MmdbReloadImplTest : public ::testing::TestWithParam, + public GeoipProviderTestBase {}; + +TEST_P(MmdbReloadImplTest, MmdbReloaded) { + MmdbReloadTestCase test_case = GetParam(); + initializeProvider(test_case.yaml_config_); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow(test_case.ip_); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + const auto& geoip_header_it = captured_lookup_response_.find(test_case.expected_header_name_); + EXPECT_EQ(test_case.expected_header_value_, geoip_header_it->second); + expectStats(test_case.db_type_, 1, 1); + std::string source_db_file_path = TestEnvironment::substitute(test_case.source_db_file_path_); + std::string reloaded_db_file_path = TestEnvironment::substitute(test_case.reloaded_db_file_path_); + TestEnvironment::renameFile(source_db_file_path, source_db_file_path + "1"); + TestEnvironment::renameFile(reloaded_db_file_path, source_db_file_path); + { + absl::MutexLock guard(&mutex_); + EXPECT_TRUE(on_changed_cbs_[0](Filesystem::Watcher::Events::MovedTo).ok()); + } + expectReloadStats(test_case.db_type_, 1, 0); + captured_lookup_response_.clear(); + remote_address = Network::Utility::parseInternetAddressNoThrow(test_case.ip_); + Geolocation::LookupRequest lookup_rq2{std::move(remote_address)}; + testing::MockFunction lookup_cb2; + auto lookup_cb_std2 = lookup_cb2.AsStdFunction(); + EXPECT_CALL(lookup_cb2, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq2), std::move(lookup_cb_std2)); + const auto& geoip_header1_it = captured_lookup_response_.find(test_case.expected_header_name_); + EXPECT_EQ(test_case.expected_reloaded_header_value_, geoip_header1_it->second); + // Clean up modifications to mmdb file names. + TestEnvironment::renameFile(source_db_file_path, reloaded_db_file_path); + TestEnvironment::renameFile(source_db_file_path + "1", source_db_file_path); +} + +TEST_P(MmdbReloadImplTest, MmdbReloadedInFlightReadsNotAffected) { + MmdbReloadTestCase test_case = GetParam(); + initializeProvider(test_case.yaml_config_); + GeoipProviderPeer::synchronizer(provider_).enable(); + const auto lookup_sync_point_name = test_case.db_type_.append("_lookup_pre_complete"); + // Start a thread that performs geoip lookup and wait in the worker thread right after reading the + // copy of mmdb instance. + GeoipProviderPeer::synchronizer(provider_).waitOn(lookup_sync_point_name); + std::thread t0([&] { + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow(test_case.ip_); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + const auto& geoip_header_it = captured_lookup_response_.find(test_case.expected_header_name_); + EXPECT_EQ(test_case.expected_header_value_, geoip_header_it->second); + // Second lookup should read the updated value. + captured_lookup_response_.clear(); + remote_address = Network::Utility::parseInternetAddressNoThrow(test_case.ip_); + Geolocation::LookupRequest lookup_rq2{std::move(remote_address)}; + testing::MockFunction lookup_cb2; + auto lookup_cb_std2 = lookup_cb2.AsStdFunction(); + EXPECT_CALL(lookup_cb2, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq2), std::move(lookup_cb_std2)); + const auto& geoip_header1_it = captured_lookup_response_.find(test_case.expected_header_name_); + EXPECT_EQ(test_case.expected_reloaded_header_value_, geoip_header1_it->second); + }); + // Wait until the thread is actually waiting. + GeoipProviderPeer::synchronizer(provider_).barrierOn(lookup_sync_point_name); + std::string source_db_file_path = TestEnvironment::substitute(test_case.source_db_file_path_); + std::string reloaded_db_file_path = TestEnvironment::substitute(test_case.reloaded_db_file_path_); + TestEnvironment::renameFile(source_db_file_path, source_db_file_path + "1"); + TestEnvironment::renameFile(reloaded_db_file_path, source_db_file_path); + { + absl::MutexLock guard(&mutex_); + EXPECT_TRUE(on_changed_cbs_[0](Filesystem::Watcher::Events::MovedTo).ok()); + } + GeoipProviderPeer::synchronizer(provider_).signal(lookup_sync_point_name); + t0.join(); + // Clean up modifications to mmdb file names. + TestEnvironment::renameFile(source_db_file_path, reloaded_db_file_path); + TestEnvironment::renameFile(source_db_file_path + "1", source_db_file_path); +} + +TEST_P(MmdbReloadImplTest, MmdbNotReloadedRuntimeFeatureDisabled) { + TestScopedRuntime scoped_runtime_; + scoped_runtime_.mergeValues({{"envoy.reloadable_features.mmdb_files_reload_enabled", "false"}}); + MmdbReloadTestCase test_case = GetParam(); + initializeProvider(test_case.yaml_config_); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow(test_case.ip_); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + const auto& geoip_header_it = captured_lookup_response_.find(test_case.expected_header_name_); + EXPECT_EQ(test_case.expected_header_value_, geoip_header_it->second); + expectStats(test_case.db_type_, 1, 1); + std::string source_db_file_path = TestEnvironment::substitute(test_case.source_db_file_path_); + std::string reloaded_db_file_path = TestEnvironment::substitute(test_case.reloaded_db_file_path_); + TestEnvironment::renameFile(source_db_file_path, source_db_file_path + "1"); + TestEnvironment::renameFile(reloaded_db_file_path, source_db_file_path); + { + absl::MutexLock guard(&mutex_); + EXPECT_EQ(0, on_changed_cbs_.size()); + } + expectReloadStats(test_case.db_type_, 0, 0); + captured_lookup_response_.clear(); + remote_address = Network::Utility::parseInternetAddressNoThrow(test_case.ip_); + Geolocation::LookupRequest lookup_rq2{std::move(remote_address)}; + testing::MockFunction lookup_cb2; + auto lookup_cb_std2 = lookup_cb2.AsStdFunction(); + EXPECT_CALL(lookup_cb2, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq2), std::move(lookup_cb_std2)); + const auto& geoip_header1_it = captured_lookup_response_.find(test_case.expected_header_name_); + EXPECT_EQ(test_case.expected_header_value_, geoip_header1_it->second); + // Clean up modifications to mmdb file names. + TestEnvironment::renameFile(source_db_file_path, reloaded_db_file_path); + TestEnvironment::renameFile(source_db_file_path + "1", source_db_file_path); +} + +struct MmdbReloadTestCase mmdb_reload_test_cases[] = { + {default_city_config_yaml, "city_db", default_city_db_path, default_updated_city_db_path, + "x-geo-city", "Boxford", "BoxfordImaginary", "78.26.243.166"}, + {default_isp_config_yaml, "isp_db", default_isp_db_path, default_updated_isp_db_path, + "x-geo-asn", "15169", "77777", "78.26.243.166"}, + {default_anon_config_yaml, "anon_db", default_anon_db_path, default_updated_anon_db_path, + "x-geo-anon", "true", "false", "65.4.3.2"}, +}; + +INSTANTIATE_TEST_SUITE_P(TestName, MmdbReloadImplTest, ::testing::ValuesIn(mmdb_reload_test_cases)); + } // namespace Maxmind } // namespace GeoipProviders } // namespace Extensions diff --git a/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test-Updated.mmdb b/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test-Updated.mmdb new file mode 100644 index 000000000000..0fc9cf03ad44 Binary files /dev/null and b/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test-Updated.mmdb differ diff --git a/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test-Updated.mmdb b/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test-Updated.mmdb new file mode 100644 index 000000000000..c8b6fe846632 Binary files /dev/null and b/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test-Updated.mmdb differ diff --git a/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test-Updated.mmdb b/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test-Updated.mmdb new file mode 100644 index 000000000000..58b6aa62baa0 Binary files /dev/null and b/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test-Updated.mmdb differ diff --git a/test/extensions/geoip_providers/maxmind/test_data/README.md b/test/extensions/geoip_providers/maxmind/test_data/README.md new file mode 100644 index 000000000000..45a6ded34c32 --- /dev/null +++ b/test/extensions/geoip_providers/maxmind/test_data/README.md @@ -0,0 +1,13 @@ +# Generating custom geoip databases + +For testing purposes one could generate geolocation databases with custom data by using [Maxmind test utility](https://github.com/maxmind/MaxMind-DB/blob/main/cmd/write-test-data/main.go). Assuming your enviroment has Golang installed, follow these steps: +* Create `source` directory on the same level as `main.go` utility and copy all geolocation db files from [source-data](https://github.com/maxmind/MaxMind-DB/tree/main/source-data) directory. +* Update the target geolocation db file (e.g. GeoIP2-City-Test.json) in newly created `source` directory with test data. +* Create `out-data` directory on the same level as `main.go` utility and run: + ``` + go run main.go --source source --target out-data + ``` +* Mmdb files should be generated in the `out-data` directory after running the command in previous step. + +# Testing mmdb lookup errors +In order to make the mmdb client to fail to read the mmdb file, one needs to provide corrupted data. The easiest way to get such data is to copy the corrupted data sample from [Maxmind repository](https://github.com/maxmind/MaxMind-DB/tree/main/bad-data), like it was done with `test_data/libmaxminddb-offset-integer-overflow.mmdb`. diff --git a/test/extensions/geoip_providers/maxmind/test_data/libmaxminddb-offset-integer-overflow.mmdb b/test/extensions/geoip_providers/maxmind/test_data/libmaxminddb-offset-integer-overflow.mmdb new file mode 100644 index 000000000000..9908afc7e2d4 --- /dev/null +++ b/test/extensions/geoip_providers/maxmind/test_data/libmaxminddb-offset-integer-overflow.mmdb @@ -0,0 +1,2369 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MaxMind-DB/bad-data/libmaxminddb/libmaxminddb-offset-integer-overflow.mmdb at main · maxmind/MaxMind-DB · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ Skip to content + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + +
+ +
+ +
+ +
+ + + + / + + MaxMind-DB + + + Public +
+ + +
+ +
+ + +
+
+ +
+
+ + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +

Latest commit

 

History

History
412 Bytes

libmaxminddb-offset-integer-overflow.mmdb

File metadata and controls

412 Bytes
+
+ + + + +
+ +
+ +
+
+ +
+ +
+

Footer

+ + + + +
+
+ + + + + © 2024 GitHub, Inc. + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+
+ +