Line data Source code
1 : #include "source/extensions/geoip_providers/maxmind/geoip_provider.h"
2 :
3 : #include "source/common/common/assert.h"
4 : #include "source/common/protobuf/protobuf.h"
5 :
6 : namespace Envoy {
7 : namespace Extensions {
8 : namespace GeoipProviders {
9 : namespace Maxmind {
10 :
11 : namespace {
12 : static constexpr const char* MMDB_CITY_LOOKUP_ARGS[] = {"city", "names", "en"};
13 : static constexpr const char* MMDB_REGION_LOOKUP_ARGS[] = {"subdivisions", "0", "iso_code"};
14 : static constexpr const char* MMDB_COUNTRY_LOOKUP_ARGS[] = {"country", "iso_code"};
15 : static constexpr const char* MMDB_ASN_LOOKUP_ARGS[] = {"autonomous_system_number"};
16 : static constexpr const char* MMDB_ANON_LOOKUP_ARGS[] = {"is_anonymous", "is_anonymous_vpn",
17 : "is_hosting_provider", "is_tor_exit_node",
18 : "is_public_proxy"};
19 : } // namespace
20 :
21 : GeoipProviderConfig::GeoipProviderConfig(
22 : const envoy::extensions::geoip_providers::maxmind::v3::MaxMindConfig& config,
23 : const std::string& stat_prefix, Stats::Scope& scope)
24 : : city_db_path_(!config.city_db_path().empty() ? absl::make_optional(config.city_db_path())
25 : : absl::nullopt),
26 : isp_db_path_(!config.isp_db_path().empty() ? absl::make_optional(config.isp_db_path())
27 : : absl::nullopt),
28 : anon_db_path_(!config.anon_db_path().empty() ? absl::make_optional(config.anon_db_path())
29 : : absl::nullopt),
30 : stats_scope_(scope.createScope(absl::StrCat(stat_prefix, "maxmind."))),
31 0 : stat_name_set_(stats_scope_->symbolTable().makeSet("Maxmind")) {
32 0 : auto geo_headers_to_add = config.common_provider_config().geo_headers_to_add();
33 0 : country_header_ = !geo_headers_to_add.country().empty()
34 0 : ? absl::make_optional(geo_headers_to_add.country())
35 0 : : absl::nullopt;
36 0 : city_header_ = !geo_headers_to_add.city().empty() ? absl::make_optional(geo_headers_to_add.city())
37 0 : : absl::nullopt;
38 0 : region_header_ = !geo_headers_to_add.region().empty()
39 0 : ? absl::make_optional(geo_headers_to_add.region())
40 0 : : absl::nullopt;
41 0 : asn_header_ = !geo_headers_to_add.asn().empty() ? absl::make_optional(geo_headers_to_add.asn())
42 0 : : absl::nullopt;
43 0 : anon_header_ = !geo_headers_to_add.is_anon().empty()
44 0 : ? absl::make_optional(geo_headers_to_add.is_anon())
45 0 : : absl::nullopt;
46 0 : anon_vpn_header_ = !geo_headers_to_add.anon_vpn().empty()
47 0 : ? absl::make_optional(geo_headers_to_add.anon_vpn())
48 0 : : absl::nullopt;
49 0 : anon_hosting_header_ = !geo_headers_to_add.anon_hosting().empty()
50 0 : ? absl::make_optional(geo_headers_to_add.anon_hosting())
51 0 : : absl::nullopt;
52 0 : anon_tor_header_ = !geo_headers_to_add.anon_tor().empty()
53 0 : ? absl::make_optional(geo_headers_to_add.anon_tor())
54 0 : : absl::nullopt;
55 0 : anon_proxy_header_ = !geo_headers_to_add.anon_proxy().empty()
56 0 : ? absl::make_optional(geo_headers_to_add.anon_proxy())
57 0 : : absl::nullopt;
58 0 : if (!city_db_path_ && !isp_db_path_ && !anon_db_path_) {
59 0 : throw EnvoyException("At least one geolocation database path needs to be configured: "
60 0 : "city_db_path, isp_db_path or anon_db_path");
61 0 : }
62 0 : if (city_db_path_) {
63 0 : registerGeoDbStats("city_db");
64 0 : }
65 0 : if (isp_db_path_) {
66 0 : registerGeoDbStats("isp_db");
67 0 : }
68 0 : if (anon_db_path_) {
69 0 : registerGeoDbStats("anon_db");
70 0 : }
71 0 : };
72 :
73 0 : void GeoipProviderConfig::registerGeoDbStats(const std::string& db_type) {
74 0 : stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".total"));
75 0 : stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".hit"));
76 0 : stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".lookup_error"));
77 0 : }
78 :
79 0 : bool GeoipProviderConfig::isLookupEnabledForHeader(const absl::optional<std::string>& header) {
80 0 : return (header && !header.value().empty());
81 0 : }
82 :
83 0 : void GeoipProviderConfig::incCounter(Stats::StatName name) {
84 0 : stats_scope_->counterFromStatName(name).inc();
85 0 : }
86 :
87 0 : GeoipProvider::~GeoipProvider() {
88 0 : ENVOY_LOG(debug, "Shutting down Maxmind geolocation provider");
89 0 : if (city_db_) {
90 0 : MMDB_close(city_db_.get());
91 0 : }
92 0 : if (isp_db_) {
93 0 : MMDB_close(isp_db_.get());
94 0 : }
95 0 : if (anon_db_) {
96 0 : MMDB_close(anon_db_.get());
97 0 : }
98 0 : }
99 :
100 0 : MaxmindDbPtr GeoipProvider::initMaxMindDb(const absl::optional<std::string>& db_path) {
101 0 : if (db_path) {
102 0 : MMDB_s maxmind_db;
103 0 : int result_code = MMDB_open(db_path.value().c_str(), MMDB_MODE_MMAP, &maxmind_db);
104 0 : RELEASE_ASSERT(MMDB_SUCCESS == result_code,
105 0 : fmt::format("Unable to open Maxmind database file {}. Error {}", db_path.value(),
106 0 : std::string(MMDB_strerror(result_code))));
107 0 : return std::make_unique<MMDB_s>(maxmind_db);
108 0 : } else {
109 0 : ENVOY_LOG(debug, "Geolocation database path is empty, skipping database creation");
110 0 : return nullptr;
111 0 : }
112 0 : }
113 :
114 : void GeoipProvider::lookup(Geolocation::LookupRequest&& request,
115 0 : Geolocation::LookupGeoHeadersCallback&& cb) const {
116 0 : auto& remote_address = request.remoteAddress();
117 0 : auto lookup_result = absl::flat_hash_map<std::string, std::string>{};
118 0 : lookupInCityDb(remote_address, lookup_result);
119 0 : lookupInAsnDb(remote_address, lookup_result);
120 0 : lookupInAnonDb(remote_address, lookup_result);
121 0 : cb(std::move(lookup_result));
122 0 : }
123 :
124 : void GeoipProvider::lookupInCityDb(
125 : const Network::Address::InstanceConstSharedPtr& remote_address,
126 0 : absl::flat_hash_map<std::string, std::string>& lookup_result) const {
127 0 : if (config_->isLookupEnabledForHeader(config_->cityHeader()) ||
128 0 : config_->isLookupEnabledForHeader(config_->regionHeader()) ||
129 0 : config_->isLookupEnabledForHeader(config_->countryHeader())) {
130 0 : ASSERT(city_db_, "Maxmind city database is not initialised for performing lookups");
131 0 : int mmdb_error;
132 0 : const uint32_t n_prev_hits = lookup_result.size();
133 0 : MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr(
134 0 : city_db_.get(), reinterpret_cast<const sockaddr*>(remote_address->sockAddr()), &mmdb_error);
135 0 : if (!mmdb_error) {
136 0 : MMDB_entry_data_list_s* entry_data_list;
137 0 : int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list);
138 0 : if (status == MMDB_SUCCESS) {
139 0 : if (config_->isLookupEnabledForHeader(config_->cityHeader())) {
140 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result, config_->cityHeader().value(),
141 0 : MMDB_CITY_LOOKUP_ARGS[0], MMDB_CITY_LOOKUP_ARGS[1],
142 0 : MMDB_CITY_LOOKUP_ARGS[2]);
143 0 : }
144 0 : if (config_->isLookupEnabledForHeader(config_->regionHeader())) {
145 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result,
146 0 : config_->regionHeader().value(), MMDB_REGION_LOOKUP_ARGS[0],
147 0 : MMDB_REGION_LOOKUP_ARGS[1], MMDB_REGION_LOOKUP_ARGS[2]);
148 0 : }
149 0 : if (config_->isLookupEnabledForHeader(config_->countryHeader())) {
150 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result,
151 0 : config_->countryHeader().value(), MMDB_COUNTRY_LOOKUP_ARGS[0],
152 0 : MMDB_COUNTRY_LOOKUP_ARGS[1]);
153 0 : }
154 0 : if (lookup_result.size() > n_prev_hits) {
155 0 : config_->incHit("city_db");
156 0 : }
157 0 : MMDB_free_entry_data_list(entry_data_list);
158 0 : }
159 :
160 0 : } else {
161 0 : config_->incLookupError("city_db");
162 0 : }
163 0 : config_->incTotal("city_db");
164 0 : }
165 0 : }
166 :
167 : void GeoipProvider::lookupInAsnDb(
168 : const Network::Address::InstanceConstSharedPtr& remote_address,
169 0 : absl::flat_hash_map<std::string, std::string>& lookup_result) const {
170 0 : if (config_->isLookupEnabledForHeader(config_->asnHeader())) {
171 0 : RELEASE_ASSERT(isp_db_, "Maxmind asn database is not initialized for performing lookups");
172 0 : int mmdb_error;
173 0 : const uint32_t n_prev_hits = lookup_result.size();
174 0 : MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr(
175 0 : isp_db_.get(), reinterpret_cast<const sockaddr*>(remote_address->sockAddr()), &mmdb_error);
176 0 : if (!mmdb_error) {
177 0 : MMDB_entry_data_list_s* entry_data_list;
178 0 : int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list);
179 0 : if (status == MMDB_SUCCESS && entry_data_list) {
180 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result, config_->asnHeader().value(),
181 0 : MMDB_ASN_LOOKUP_ARGS[0]);
182 0 : MMDB_free_entry_data_list(entry_data_list);
183 0 : if (lookup_result.size() > n_prev_hits) {
184 0 : config_->incHit("isp_db");
185 0 : }
186 0 : } else {
187 0 : config_->incLookupError("isp_db");
188 0 : }
189 0 : }
190 0 : config_->incTotal("isp_db");
191 0 : }
192 0 : }
193 :
194 : void GeoipProvider::lookupInAnonDb(
195 : const Network::Address::InstanceConstSharedPtr& remote_address,
196 0 : absl::flat_hash_map<std::string, std::string>& lookup_result) const {
197 0 : if (config_->isLookupEnabledForHeader(config_->anonHeader()) || config_->anonVpnHeader()) {
198 0 : ASSERT(anon_db_, "Maxmind city database is not initialised for performing lookups");
199 0 : int mmdb_error;
200 0 : const uint32_t n_prev_hits = lookup_result.size();
201 0 : MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr(
202 0 : anon_db_.get(), reinterpret_cast<const sockaddr*>(remote_address->sockAddr()), &mmdb_error);
203 0 : if (!mmdb_error) {
204 0 : MMDB_entry_data_list_s* entry_data_list;
205 0 : int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list);
206 0 : if (status == MMDB_SUCCESS) {
207 0 : if (config_->isLookupEnabledForHeader(config_->anonHeader())) {
208 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result, config_->anonHeader().value(),
209 0 : MMDB_ANON_LOOKUP_ARGS[0]);
210 0 : }
211 0 : if (config_->isLookupEnabledForHeader(config_->anonVpnHeader())) {
212 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result,
213 0 : config_->anonVpnHeader().value(), MMDB_ANON_LOOKUP_ARGS[1]);
214 0 : }
215 0 : if (config_->isLookupEnabledForHeader(config_->anonHostingHeader())) {
216 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result,
217 0 : config_->anonHostingHeader().value(), MMDB_ANON_LOOKUP_ARGS[2]);
218 0 : }
219 0 : if (config_->isLookupEnabledForHeader(config_->anonTorHeader())) {
220 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result,
221 0 : config_->anonTorHeader().value(), MMDB_ANON_LOOKUP_ARGS[3]);
222 0 : }
223 0 : if (config_->isLookupEnabledForHeader(config_->anonProxyHeader())) {
224 0 : populateGeoLookupResult(mmdb_lookup_result, lookup_result,
225 0 : config_->anonProxyHeader().value(), MMDB_ANON_LOOKUP_ARGS[4]);
226 0 : }
227 0 : if (lookup_result.size() > n_prev_hits) {
228 0 : config_->incHit("anon_db");
229 0 : }
230 0 : MMDB_free_entry_data_list(entry_data_list);
231 0 : } else {
232 0 : config_->incLookupError("anon_db");
233 0 : }
234 0 : }
235 0 : config_->incTotal("anon_db");
236 0 : }
237 0 : }
238 :
239 : template <class... Params>
240 : void GeoipProvider::populateGeoLookupResult(
241 : MMDB_lookup_result_s& mmdb_lookup_result,
242 : absl::flat_hash_map<std::string, std::string>& lookup_result, const std::string& result_key,
243 0 : Params... lookup_params) const {
244 0 : MMDB_entry_data_s entry_data;
245 0 : if ((MMDB_get_value(&mmdb_lookup_result.entry, &entry_data, lookup_params..., NULL)) ==
246 0 : MMDB_SUCCESS) {
247 0 : std::string result_value;
248 0 : if (entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
249 0 : result_value = std::string(entry_data.utf8_string, entry_data.data_size);
250 0 : } else if (entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UINT32 &&
251 0 : entry_data.uint32 > 0) {
252 0 : result_value = std::to_string(entry_data.uint32);
253 0 : } else if (entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_BOOLEAN) {
254 0 : result_value = entry_data.boolean ? "true" : "false";
255 0 : }
256 0 : if (!result_value.empty()) {
257 0 : lookup_result.insert(std::make_pair(result_key, result_value));
258 0 : }
259 0 : }
260 0 : }
261 :
262 : } // namespace Maxmind
263 : } // namespace GeoipProviders
264 : } // namespace Extensions
265 : } // namespace Envoy
|