Skip to content
Open
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
10 changes: 5 additions & 5 deletions plugins/header_rewrite/conditions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -831,14 +831,14 @@ ConditionNow::eval(const Resources &res)
}

std::string
ConditionGeo::get_geo_string(const sockaddr * /* addr ATS_UNUSED */) const
ConditionGeo::get_geo_string(const sockaddr * /* addr ATS_UNUSED */, void * /* geo_handle ATS_UNUSED */) const
{
TSError("[%s] No Geo library available!", PLUGIN_NAME);
return "";
}

int64_t
ConditionGeo::get_geo_int(const sockaddr * /* addr ATS_UNUSED */) const
ConditionGeo::get_geo_int(const sockaddr * /* addr ATS_UNUSED */, void * /* geo_handle ATS_UNUSED */) const
{
TSError("[%s] No Geo library available!", PLUGIN_NAME);
return 0;
Expand Down Expand Up @@ -891,9 +891,9 @@ void
ConditionGeo::append_value(std::string &s, const Resources &res)
{
if (is_int_type()) {
s += std::to_string(get_geo_int(getClientAddr(res.state.txnp, _txn_private_slot)));
s += std::to_string(get_geo_int(getClientAddr(res.state.txnp, _txn_private_slot), res.geo_handle));
} else {
s += get_geo_string(getClientAddr(res.state.txnp, _txn_private_slot));
s += get_geo_string(getClientAddr(res.state.txnp, _txn_private_slot), res.geo_handle);
}
Dbg(pi_dbg_ctl, "Appending GEO() to evaluation value -> %s", s.c_str());
}
Expand All @@ -905,7 +905,7 @@ ConditionGeo::eval(const Resources &res)

Dbg(pi_dbg_ctl, "Evaluating GEO()");
if (is_int_type()) {
int64_t geo = get_geo_int(getClientAddr(res.state.txnp, _txn_private_slot));
int64_t geo = get_geo_int(getClientAddr(res.state.txnp, _txn_private_slot), res.geo_handle);

ret = static_cast<const Matchers<int64_t> *>(_matcher.get())->test(geo, res);
} else {
Expand Down
4 changes: 2 additions & 2 deletions plugins/header_rewrite/conditions.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,8 @@ class ConditionGeo : public Condition
}

private:
virtual int64_t get_geo_int(const sockaddr *addr) const;
virtual std::string get_geo_string(const sockaddr *addr) const;
virtual int64_t get_geo_int(const sockaddr *addr, void *geo_handle) const;
virtual std::string get_geo_string(const sockaddr *addr, void *geo_handle) const;

protected:
bool
Expand Down
12 changes: 6 additions & 6 deletions plugins/header_rewrite/conditions_geo.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ class MMConditionGeo : public ConditionGeo
MMConditionGeo() {}
virtual ~MMConditionGeo() {}

static void initLibrary(const std::string &path);
static void *initLibrary(const std::string &path);

virtual int64_t get_geo_int(const sockaddr *addr) const override;
virtual std::string get_geo_string(const sockaddr *addr) const override;
int64_t get_geo_int(const sockaddr *addr, void *geo_handle) const override;
std::string get_geo_string(const sockaddr *addr, void *geo_handle) const override;
};

class GeoIPConditionGeo : public ConditionGeo
Expand All @@ -39,8 +39,8 @@ class GeoIPConditionGeo : public ConditionGeo
GeoIPConditionGeo() {}
virtual ~GeoIPConditionGeo() {}

static void initLibrary(const std::string &path);
static void *initLibrary(const std::string &path);

virtual int64_t get_geo_int(const sockaddr *addr) const override;
virtual std::string get_geo_string(const sockaddr *addr) const override;
int64_t get_geo_int(const sockaddr *addr, void *geo_handle) const override;
std::string get_geo_string(const sockaddr *addr, void *geo_handle) const override;
};
71 changes: 45 additions & 26 deletions plugins/header_rewrite/conditions_geo_geoip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,56 +24,73 @@
#include <unistd.h>
#include <arpa/inet.h>
#include <cctype>
#include <mutex>

#include "ts/ts.h"

#include "conditions_geo.h"

#include <GeoIP.h>

GeoIP *gGeoIP[NUM_DB_TYPES];
struct GeoIPHandleSet {
GeoIP *dbs[NUM_DB_TYPES] = {};
};

void
static std::mutex gGeoIPCacheMutex;
static GeoIPHandleSet *gGeoIPHandleSet = nullptr;

void *
GeoIPConditionGeo::initLibrary(const std::string &)
{
std::lock_guard<std::mutex> lock(gGeoIPCacheMutex);

if (gGeoIPHandleSet != nullptr) {
return gGeoIPHandleSet;
}

gGeoIPHandleSet = new GeoIPHandleSet;

GeoIPDBTypes dbs[] = {GEOIP_COUNTRY_EDITION, GEOIP_COUNTRY_EDITION_V6, GEOIP_ASNUM_EDITION, GEOIP_ASNUM_EDITION_V6};

for (auto &db : dbs) {
if (!gGeoIP[db] && GeoIP_db_avail(db)) {
// GEOIP_STANDARD seems to break threaded apps...
gGeoIP[db] = GeoIP_open_type(db, GEOIP_MMAP_CACHE);
if (!gGeoIPHandleSet->dbs[db] && GeoIP_db_avail(db)) {
gGeoIPHandleSet->dbs[db] = GeoIP_open_type(db, GEOIP_MMAP_CACHE);

char *db_info = GeoIP_database_info(gGeoIP[db]);
char *db_info = GeoIP_database_info(gGeoIPHandleSet->dbs[db]);
Dbg(pi_dbg_ctl, "initialized GeoIP-DB[%d] %s", db, db_info);
free(db_info);
}
}

return gGeoIPHandleSet;
}

std::string
GeoIPConditionGeo::get_geo_string(const sockaddr *addr) const
GeoIPConditionGeo::get_geo_string(const sockaddr *addr, void *geo_handle) const
{
std::string ret = "(unknown)";
int v = 4;

if (addr) {
auto *handle = static_cast<GeoIPHandleSet *>(geo_handle);

if (addr && handle) {
switch (_geo_qual) {
// Country database
case GEO_QUAL_COUNTRY:
switch (addr->sa_family) {
case AF_INET:
if (gGeoIP[GEOIP_COUNTRY_EDITION]) {
if (handle->dbs[GEOIP_COUNTRY_EDITION]) {
uint32_t ip = ntohl(reinterpret_cast<const struct sockaddr_in *>(addr)->sin_addr.s_addr);

ret = GeoIP_country_code_by_ipnum(gGeoIP[GEOIP_COUNTRY_EDITION], ip);
ret = GeoIP_country_code_by_ipnum(handle->dbs[GEOIP_COUNTRY_EDITION], ip);
}
break;
case AF_INET6: {
if (gGeoIP[GEOIP_COUNTRY_EDITION_V6]) {
if (handle->dbs[GEOIP_COUNTRY_EDITION_V6]) {
geoipv6_t ip = reinterpret_cast<const struct sockaddr_in6 *>(addr)->sin6_addr;

v = 6;
ret = GeoIP_country_code_by_ipnum_v6(gGeoIP[GEOIP_COUNTRY_EDITION_V6], ip);
ret = GeoIP_country_code_by_ipnum_v6(handle->dbs[GEOIP_COUNTRY_EDITION_V6], ip);
}
} break;
default:
Expand All @@ -86,18 +103,18 @@ GeoIPConditionGeo::get_geo_string(const sockaddr *addr) const
case GEO_QUAL_ASN_NAME:
switch (addr->sa_family) {
case AF_INET:
if (gGeoIP[GEOIP_ASNUM_EDITION]) {
if (handle->dbs[GEOIP_ASNUM_EDITION]) {
uint32_t ip = ntohl(reinterpret_cast<const struct sockaddr_in *>(addr)->sin_addr.s_addr);

ret = GeoIP_name_by_ipnum(gGeoIP[GEOIP_ASNUM_EDITION], ip);
ret = GeoIP_name_by_ipnum(handle->dbs[GEOIP_ASNUM_EDITION], ip);
}
break;
case AF_INET6: {
if (gGeoIP[GEOIP_ASNUM_EDITION_V6]) {
if (handle->dbs[GEOIP_ASNUM_EDITION_V6]) {
geoipv6_t ip = reinterpret_cast<const struct sockaddr_in6 *>(addr)->sin6_addr;

v = 6;
ret = GeoIP_name_by_ipnum_v6(gGeoIP[GEOIP_ASNUM_EDITION_V6], ip);
ret = GeoIP_name_by_ipnum_v6(handle->dbs[GEOIP_ASNUM_EDITION_V6], ip);
}
} break;
default:
Expand All @@ -114,12 +131,14 @@ GeoIPConditionGeo::get_geo_string(const sockaddr *addr) const
}

int64_t
GeoIPConditionGeo::get_geo_int(const sockaddr *addr) const
GeoIPConditionGeo::get_geo_int(const sockaddr *addr, void *geo_handle) const
{
int64_t ret = -1;
int v = 4;

if (!addr) {
auto *handle = static_cast<GeoIPHandleSet *>(geo_handle);

if (!addr || !handle) {
return 0;
}

Expand All @@ -128,18 +147,18 @@ GeoIPConditionGeo::get_geo_int(const sockaddr *addr) const
case GEO_QUAL_COUNTRY_ISO:
switch (addr->sa_family) {
case AF_INET:
if (gGeoIP[GEOIP_COUNTRY_EDITION]) {
if (handle->dbs[GEOIP_COUNTRY_EDITION]) {
uint32_t ip = ntohl(reinterpret_cast<const struct sockaddr_in *>(addr)->sin_addr.s_addr);

ret = GeoIP_id_by_ipnum(gGeoIP[GEOIP_COUNTRY_EDITION], ip);
ret = GeoIP_id_by_ipnum(handle->dbs[GEOIP_COUNTRY_EDITION], ip);
}
break;
case AF_INET6: {
if (gGeoIP[GEOIP_COUNTRY_EDITION_V6]) {
if (handle->dbs[GEOIP_COUNTRY_EDITION_V6]) {
geoipv6_t ip = reinterpret_cast<const struct sockaddr_in6 *>(addr)->sin6_addr;

v = 6;
ret = GeoIP_id_by_ipnum_v6(gGeoIP[GEOIP_COUNTRY_EDITION_V6], ip);
ret = GeoIP_id_by_ipnum_v6(handle->dbs[GEOIP_COUNTRY_EDITION_V6], ip);
}
} break;
default:
Expand All @@ -153,18 +172,18 @@ GeoIPConditionGeo::get_geo_int(const sockaddr *addr) const

switch (addr->sa_family) {
case AF_INET:
if (gGeoIP[GEOIP_ASNUM_EDITION]) {
if (handle->dbs[GEOIP_ASNUM_EDITION]) {
uint32_t ip = ntohl(reinterpret_cast<const struct sockaddr_in *>(addr)->sin_addr.s_addr);

asn_name = GeoIP_name_by_ipnum(gGeoIP[GEOIP_ASNUM_EDITION], ip);
asn_name = GeoIP_name_by_ipnum(handle->dbs[GEOIP_ASNUM_EDITION], ip);
}
break;
case AF_INET6:
if (gGeoIP[GEOIP_ASNUM_EDITION_V6]) {
if (handle->dbs[GEOIP_ASNUM_EDITION_V6]) {
geoipv6_t ip = reinterpret_cast<const struct sockaddr_in6 *>(addr)->sin6_addr;

v = 6;
asn_name = GeoIP_name_by_ipnum_v6(gGeoIP[GEOIP_ASNUM_EDITION_V6], ip);
asn_name = GeoIP_name_by_ipnum_v6(handle->dbs[GEOIP_ASNUM_EDITION_V6], ip);
}
break;
}
Expand Down
65 changes: 41 additions & 24 deletions plugins/header_rewrite/conditions_geo_maxmind.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,25 @@

#include <unistd.h>
#include <arpa/inet.h>
#include <map>
#include <mutex>
#include <string>

#include "ts/ts.h"

#include "conditions_geo.h"

#include <maxminddb.h>

MMDB_s *gMaxMindDB = nullptr;

enum class MmdbSchema { NESTED, FLAT };
static MmdbSchema gMmdbSchema = MmdbSchema::NESTED;

struct MmdbHandle {
MMDB_s db;
MmdbSchema schema = MmdbSchema::NESTED;
};

static std::map<std::string, MmdbHandle *> gMmdbCache;
static std::mutex gMmdbCacheMutex;

Comment on lines +38 to 45
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new MaxMind handle cache stores heap-allocated MmdbHandle instances (and their MMDB_s mmaps) for the lifetime of the process, but there is no corresponding MMDB_close()/delete path. This can lead to unbounded growth in mapped memory / open files if remap instances are recreated over time with different --geo-db-path values. Consider adding reference counting and cleanup (e.g., close+erase when the last RulesConfig using a path is deleted), or a bounded/evicting cache with explicit teardown on shutdown/reload.

Copilot uses AI. Check for mistakes.
// Detect whether the MMDB uses nested (GeoLite2) or flat (vendor) field layout
// by probing for the nested country path on a lookup result.
Expand All @@ -57,56 +65,63 @@ detect_schema(MMDB_entry_s *entry)

static const char *probe_ips[] = {"8.8.8.8", "1.1.1.1", "128.0.0.1"};

void
void *
MMConditionGeo::initLibrary(const std::string &path)
{
if (path.empty()) {
Dbg(pi_dbg_ctl, "Empty MaxMind db path specified. Not initializing!");
return;
return nullptr;
}

if (gMaxMindDB != nullptr) {
Dbg(pi_dbg_ctl, "Maxmind library already initialized");
return;
std::lock_guard<std::mutex> lock(gMmdbCacheMutex);

auto it = gMmdbCache.find(path);
if (it != gMmdbCache.end()) {
Dbg(pi_dbg_ctl, "Maxmind library already initialized for %s", path.c_str());
return it->second;
}

gMaxMindDB = new MMDB_s;
auto *handle = new MmdbHandle;
int status = MMDB_open(path.c_str(), MMDB_MODE_MMAP, &handle->db);

int status = MMDB_open(path.c_str(), MMDB_MODE_MMAP, gMaxMindDB);
if (MMDB_SUCCESS != status) {
Dbg(pi_dbg_ctl, "Cannot open %s - %s", path.c_str(), MMDB_strerror(status));
delete gMaxMindDB;
gMaxMindDB = nullptr;
return;
delete handle;
return nullptr;
}

// Probe the database schema at load time so we know which field paths to
// use for country lookups. Try a few well-known IPs until one hits.
for (auto *ip : probe_ips) {
int gai_error, mmdb_error;
MMDB_lookup_result_s result = MMDB_lookup_string(gMaxMindDB, ip, &gai_error, &mmdb_error);
MMDB_lookup_result_s result = MMDB_lookup_string(&handle->db, ip, &gai_error, &mmdb_error);
if (gai_error == 0 && MMDB_SUCCESS == mmdb_error && result.found_entry) {
gMmdbSchema = detect_schema(&result.entry);
Dbg(pi_dbg_ctl, "Loaded %s (schema: %s)", path.c_str(), gMmdbSchema == MmdbSchema::FLAT ? "flat" : "nested");
return;
handle->schema = detect_schema(&result.entry);
Dbg(pi_dbg_ctl, "Loaded %s (schema: %s)", path.c_str(), handle->schema == MmdbSchema::FLAT ? "flat" : "nested");
gMmdbCache[path] = handle;
return handle;
}
}

Dbg(pi_dbg_ctl, "Loaded %s (schema: defaulting to nested, no probe IPs matched)", path.c_str());
gMmdbCache[path] = handle;
return handle;
}

std::string
MMConditionGeo::get_geo_string(const sockaddr *addr) const
MMConditionGeo::get_geo_string(const sockaddr *addr, void *geo_handle) const
{
std::string ret = "(unknown)";
int mmdb_error;

if (gMaxMindDB == nullptr) {
auto *handle = static_cast<MmdbHandle *>(geo_handle);

if (handle == nullptr) {
Dbg(pi_dbg_ctl, "MaxMind not initialized; using default value");
return ret;
}

MMDB_lookup_result_s result = MMDB_lookup_sockaddr(gMaxMindDB, addr, &mmdb_error);
MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&handle->db, addr, &mmdb_error);

if (MMDB_SUCCESS != mmdb_error) {
Dbg(pi_dbg_ctl, "Error during sockaddr lookup: %s", MMDB_strerror(mmdb_error));
Expand All @@ -123,7 +138,7 @@ MMConditionGeo::get_geo_string(const sockaddr *addr) const

switch (_geo_qual) {
case GEO_QUAL_COUNTRY:
if (gMmdbSchema == MmdbSchema::FLAT) {
if (handle->schema == MmdbSchema::FLAT) {
status = MMDB_get_value(&result.entry, &entry_data, "country_code", NULL);
} else {
status = MMDB_get_value(&result.entry, &entry_data, "country", "iso_code", NULL);
Expand All @@ -150,17 +165,19 @@ MMConditionGeo::get_geo_string(const sockaddr *addr) const
}

int64_t
MMConditionGeo::get_geo_int(const sockaddr *addr) const
MMConditionGeo::get_geo_int(const sockaddr *addr, void *geo_handle) const
{
int64_t ret = -1;
int mmdb_error;

if (gMaxMindDB == nullptr) {
auto *handle = static_cast<MmdbHandle *>(geo_handle);

if (handle == nullptr) {
Dbg(pi_dbg_ctl, "MaxMind not initialized; using default value");
return ret;
}

MMDB_lookup_result_s result = MMDB_lookup_sockaddr(gMaxMindDB, addr, &mmdb_error);
MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&handle->db, addr, &mmdb_error);

if (MMDB_SUCCESS != mmdb_error) {
Dbg(pi_dbg_ctl, "Error during sockaddr lookup: %s", MMDB_strerror(mmdb_error));
Expand Down
Loading
Loading