From 48b418c7eb03181c02ce830e2061d3f56b08598e Mon Sep 17 00:00:00 2001 From: Randall Meyer Date: Wed, 7 Apr 2021 16:22:45 -0700 Subject: [PATCH] header_rewrite: allow for use of maxminddb as source of geo truth --- configure.ac | 49 ++++- doc/admin-guide/plugins/header_rewrite.en.rst | 11 +- include/tscore/ink_config.h.in | 3 + plugins/Makefile.am | 4 +- plugins/experimental/geoip_acl/Makefile.inc | 2 +- plugins/header_rewrite/Makefile.inc | 26 ++- plugins/header_rewrite/conditions.cc | 152 +------------- plugins/header_rewrite/conditions.h | 8 +- plugins/header_rewrite/conditions_geo.h | 46 +++++ .../header_rewrite/conditions_geo_geoip.cc | 188 ++++++++++++++++++ .../header_rewrite/conditions_geo_maxmind.cc | 175 ++++++++++++++++ plugins/header_rewrite/factory.cc | 7 + plugins/header_rewrite/header_rewrite.cc | 83 +++++--- plugins/header_rewrite/resources.h | 5 - plugins/healthchecks/Makefile.inc | 4 - 15 files changed, 560 insertions(+), 203 deletions(-) create mode 100644 plugins/header_rewrite/conditions_geo.h create mode 100644 plugins/header_rewrite/conditions_geo_geoip.cc create mode 100644 plugins/header_rewrite/conditions_geo_maxmind.cc diff --git a/configure.ac b/configure.ac index 269973406c0..514fc5ac2f3 100644 --- a/configure.ac +++ b/configure.ac @@ -1685,12 +1685,16 @@ AC_SUBST(use_hwloc) # AC_CHECK_HEADERS([GeoIP.h], [ AC_CHECK_LIB([GeoIP], [GeoIP_new], [ - AC_SUBST([GEO_LIBS], ["-lGeoIP"]) + AC_SUBST([GEOIP_LIBS], ["-lGeoIP"]) + AC_SUBST(has_geoip, 1) ], [ - AC_SUBST([GEO_LIBS], [""]) + AC_SUBST([GEOIP_LIBS], [""]) + AC_SUBST(has_geoip, 0) ]) ]) +AM_CONDITIONAL([HAS_GEOIP], [test "x${has_geoip}" = "x1" ]) + # # Check for libmaxmind. This is the maxmind v2 API where GeoIP is the legacy # v1 dat file based API @@ -1698,14 +1702,49 @@ AC_CHECK_HEADERS([GeoIP.h], [ AC_CHECK_HEADERS([maxminddb.h], [ AC_CHECK_LIB([maxminddb], [MMDB_open], [ AC_SUBST([MAXMINDDB_LIBS], ["-lmaxminddb"]) - AC_SUBST(has_maxmind, 1) + AC_SUBST(has_maxminddb, 1) ], [ AC_SUBST([MAXMINDDB_LIBS], [""]) - AC_SUBST(has_maxmind, 0) + AC_SUBST(has_maxminddb, 0) ]) ]) -AM_CONDITIONAL([BUILD_MAXMIND_ACL_PLUGIN], [test "x${has_maxmind}" = "x1" ]) +AM_CONDITIONAL([HAS_MAXMINDDB], [test "x${has_maxminddb}" = "x1" ]) + +AC_ARG_WITH([hrw-geo-provider], + [AS_HELP_STRING([--with-hrw-geo-provider=geoip|maxminddb],[geo provider to use with header_rewrite [default=auto] ])], + [geo_provider=$withval], + [geo_provider="auto"] +) +use_hrw_geoip=0 +use_hrw_maxminddb=0 + +AS_IF([test "x$geo_provider" = "xauto"], [ + if test "x$has_geoip" = "x1"; then + use_hrw_geoip=1 + AC_MSG_NOTICE([Using GeoIP interface for header_rewrite]) + elif test "x$has_maxminddb" = "x1"; then + use_hrw_maxminddb=1 + AC_MSG_NOTICE([Using MaxMindDB interface for header_rewrite]) + fi +],[ + case "x$geo_provider" in + xgeoip) + use_hrw_geoip=1 + AC_MSG_RESULT([forced to GeoIP]) + ;; + xmaxminddb) + use_hrw_maxminddb=1 + AC_MSG_RESULT([forced to MaxMindDB]) + ;; + *) + AC_MSG_RESULT([failed]) + AC_MSG_FAILURE([unknown geo interface $geo_provider]) + esac +]) + +AC_SUBST(use_hrw_geoip) +AC_SUBST(use_hrw_maxminddb) # Right now, the healthcheck plugins requires inotify_init (and friends) AM_CONDITIONAL([BUILD_HEALTHCHECK_PLUGIN], [ test "$ac_cv_func_inotify_init" = "yes" ]) diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst b/doc/admin-guide/plugins/header_rewrite.en.rst index 40f86a342bb..5caa2701738 100644 --- a/doc/admin-guide/plugins/header_rewrite.en.rst +++ b/doc/admin-guide/plugins/header_rewrite.en.rst @@ -82,11 +82,16 @@ This plugin may be enabled globally, so that the conditions and header rewriting rules are evaluated for every request made to your |TS| instance. This is done by adding the following line to your :file:`plugin.config`:: - header_rewrite.so config_file_1.conf config_file_2.conf ... + header_rewrite.so [--geo-db-path=path/to/geoip.db] config_file_1.conf config_file_2.conf ... You may specify multiple configuration files. Their rules will be evaluated in the order the files are listed. +The plugin takes an optional switch ``--geo-db-path``. If MaxMindDB support has +been compiled in, use this switch to point at your .mmdb file. This also applies to +the remap context. + + Enabling Per-Mapping -------------------- @@ -219,7 +224,7 @@ GEO cond %{GEO:} Perform a GeoIP lookup of the client-IP, using a 3rd party library and -DB. Currently only the MaxMind GeoIP API is supported. The default is to +DB. Currently the MaxMind GeoIP and MaxMindDB APIs are supported. The default is to do a Country lookup, but the following qualifiers are supported:: %{GEO:COUNTRY} The country code (e.g. "US") @@ -495,7 +500,7 @@ TCP-INFO ~~~~~~~~ :: - cond %{} + cond %{} add-header @PropertyName "%{TCP-INFO}" This operation records TCP Info struct field values as an Internal remap as well as global header at the event hook specified by the condition. Supported hook conditions include TXN_START_HOOK, SEND_RESPONSE_HEADER_HOOK and TXN_CLOSE_HOOK in the Global plugin and REMAP_PSEUDO_HOOK, SEND_RESPONSE_HEADER_HOOK and TXN_CLOSE_HOOK in the Remap plugin. Conditions supported as request headers include TXN_START_HOOK and REMAP_PSEUDO_HOOK. The other conditions are supported as response headers. TCP Info fields currently recorded include rtt, rto, snd_cwnd and all_retrans. This operation is not supported on transactions originated within Traffic Server (for e.g using the |TS| :c:func:`TSHttpTxnIsInternal`) diff --git a/include/tscore/ink_config.h.in b/include/tscore/ink_config.h.in index 8b80172c2f4..c23417f2441 100644 --- a/include/tscore/ink_config.h.in +++ b/include/tscore/ink_config.h.in @@ -81,6 +81,9 @@ #define TS_HAS_TLS_EARLY_DATA @has_tls_early_data@ #define TS_HAS_TLS_SESSION_TICKET @has_tls_session_ticket@ +#define TS_USE_HRW_GEOIP @use_hrw_geoip@ +#define TS_USE_HRW_MAXMINDDB @use_hrw_maxminddb@ + #define TS_HAS_SO_PEERCRED @has_so_peercred@ /* OS API definitions */ diff --git a/plugins/Makefile.am b/plugins/Makefile.am index b739a05370f..76addc5914a 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -39,7 +39,9 @@ include esi/Makefile.inc include generator/Makefile.inc include compress/Makefile.inc include header_rewrite/Makefile.inc +if BUILD_HEALTHCHECK_PLUGIN include healthchecks/Makefile.inc +endif include libloader/Makefile.inc if HAS_LUAJIT include lua/Makefile.inc @@ -71,7 +73,7 @@ include experimental/hook-trace/Makefile.inc include experimental/icap/Makefile.inc include experimental/inliner/Makefile.inc -if BUILD_MAXMIND_ACL_PLUGIN +if HAS_MAXMINDDB include experimental/maxmind_acl/Makefile.inc endif diff --git a/plugins/experimental/geoip_acl/Makefile.inc b/plugins/experimental/geoip_acl/Makefile.inc index 3ff84f67f7b..150b3c3bf19 100644 --- a/plugins/experimental/geoip_acl/Makefile.inc +++ b/plugins/experimental/geoip_acl/Makefile.inc @@ -20,4 +20,4 @@ experimental_geoip_acl_geoip_acl_la_SOURCES = \ experimental/geoip_acl/acl.cc \ experimental/geoip_acl/geoip_acl.cc -experimental_geoip_acl_geoip_acl_la_LIBADD = $(GEO_LIBS) +experimental_geoip_acl_geoip_acl_la_LIBADD = $(GEOIP_LIBS) diff --git a/plugins/header_rewrite/Makefile.inc b/plugins/header_rewrite/Makefile.inc index 0b5cfe8bb62..a64fffa93d6 100644 --- a/plugins/header_rewrite/Makefile.inc +++ b/plugins/header_rewrite/Makefile.inc @@ -44,16 +44,38 @@ header_rewrite_header_rewrite_la_SOURCES = \ header_rewrite/value.cc \ header_rewrite/value.h +if HAS_MAXMINDDB +header_rewrite_header_rewrite_la_SOURCES += header_rewrite/conditions_geo_maxmind.cc +endif + +if HAS_GEOIP +header_rewrite_header_rewrite_la_SOURCES += header_rewrite/conditions_geo_geoip.cc +endif + header_rewrite_parser_la_SOURCES = \ header_rewrite/parser.cc \ header_rewrite/parser.h header_rewrite_header_rewrite_la_LIBADD = \ - header_rewrite/parser.la \ - $(GEO_LIBS) + header_rewrite/parser.la + +if HAS_GEOIP +header_rewrite_header_rewrite_la_LIBADD += $(GEOIP_LIBS) +endif + +if HAS_MAXMINDDB +header_rewrite_header_rewrite_la_LIBADD += $(MAXMINDDB_LIBS) +endif check_PROGRAMS += header_rewrite/header_rewrite_test header_rewrite_header_rewrite_test_SOURCES = \ header_rewrite/header_rewrite_test.cc header_rewrite_header_rewrite_test_LDADD = \ header_rewrite/parser.la +if HAS_GEOIP +header_rewrite_header_rewrite_test_LDADD += $(GEOIP_LIBS) +endif + +if HAS_MAXMINDDB +header_rewrite_header_rewrite_test_LDADD += $(MAXMINDDB_LIBS) +endif diff --git a/plugins/header_rewrite/conditions.cc b/plugins/header_rewrite/conditions.cc index 66c3e5d9173..f88dadcb9c8 100644 --- a/plugins/header_rewrite/conditions.cc +++ b/plugins/header_rewrite/conditions.cc @@ -15,10 +15,12 @@ See the License for the specific language governing permissions and limitations under the License. */ + ////////////////////////////////////////////////////////////////////////////////////////////// // conditions.cc: Implementation of the condition classes // // + #include #include #include @@ -740,155 +742,11 @@ ConditionNow::eval(const Resources &res) return static_cast(_matcher)->test(now); } -// ConditionGeo: Geo-based information (integer). See ConditionGeoCountry for the string version. -#if HAVE_GEOIP_H -const char * -ConditionGeo::get_geo_string(const sockaddr *addr) const -{ - const char *ret = "(unknown)"; - int v = 4; - - if (addr) { - switch (_geo_qual) { - // Country database - case GEO_QUAL_COUNTRY: - switch (addr->sa_family) { - case AF_INET: - if (gGeoIP[GEOIP_COUNTRY_EDITION]) { - uint32_t ip = ntohl(reinterpret_cast(addr)->sin_addr.s_addr); - - ret = GeoIP_country_code_by_ipnum(gGeoIP[GEOIP_COUNTRY_EDITION], ip); - } - break; - case AF_INET6: { - if (gGeoIP[GEOIP_COUNTRY_EDITION_V6]) { - geoipv6_t ip = reinterpret_cast(addr)->sin6_addr; - - v = 6; - ret = GeoIP_country_code_by_ipnum_v6(gGeoIP[GEOIP_COUNTRY_EDITION_V6], ip); - } - } break; - default: - break; - } - TSDebug(PLUGIN_NAME, "eval(): Client IPv%d seems to come from Country: %s", v, ret); - break; - - // ASN database - case GEO_QUAL_ASN_NAME: - switch (addr->sa_family) { - case AF_INET: - if (gGeoIP[GEOIP_ASNUM_EDITION]) { - uint32_t ip = ntohl(reinterpret_cast(addr)->sin_addr.s_addr); - - ret = GeoIP_name_by_ipnum(gGeoIP[GEOIP_ASNUM_EDITION], ip); - } - break; - case AF_INET6: { - if (gGeoIP[GEOIP_ASNUM_EDITION_V6]) { - geoipv6_t ip = reinterpret_cast(addr)->sin6_addr; - - v = 6; - ret = GeoIP_name_by_ipnum_v6(gGeoIP[GEOIP_ASNUM_EDITION_V6], ip); - } - } break; - default: - break; - } - TSDebug(PLUGIN_NAME, "eval(): Client IPv%d seems to come from ASN Name: %s", v, ret); - break; - - default: - break; - } - } - - return ret ? ret : "(unknown)"; -} - -int64_t -ConditionGeo::get_geo_int(const sockaddr *addr) const -{ - int64_t ret = -1; - int v = 4; - - if (!addr) { - return 0; - } - - switch (_geo_qual) { - // Country Database - case GEO_QUAL_COUNTRY_ISO: - switch (addr->sa_family) { - case AF_INET: - if (gGeoIP[GEOIP_COUNTRY_EDITION]) { - uint32_t ip = ntohl(reinterpret_cast(addr)->sin_addr.s_addr); - - ret = GeoIP_id_by_ipnum(gGeoIP[GEOIP_COUNTRY_EDITION], ip); - } - break; - case AF_INET6: { - if (gGeoIP[GEOIP_COUNTRY_EDITION_V6]) { - geoipv6_t ip = reinterpret_cast(addr)->sin6_addr; - - v = 6; - ret = GeoIP_id_by_ipnum_v6(gGeoIP[GEOIP_COUNTRY_EDITION_V6], ip); - } - } break; - default: - break; - } - TSDebug(PLUGIN_NAME, "eval(): Client IPv%d seems to come from Country ISO: %" PRId64, v, ret); - break; - - case GEO_QUAL_ASN: { - const char *asn_name = nullptr; - - switch (addr->sa_family) { - case AF_INET: - if (gGeoIP[GEOIP_ASNUM_EDITION]) { - uint32_t ip = ntohl(reinterpret_cast(addr)->sin_addr.s_addr); - - asn_name = GeoIP_name_by_ipnum(gGeoIP[GEOIP_ASNUM_EDITION], ip); - } - break; - case AF_INET6: - if (gGeoIP[GEOIP_ASNUM_EDITION_V6]) { - geoipv6_t ip = reinterpret_cast(addr)->sin6_addr; - - v = 6; - asn_name = GeoIP_name_by_ipnum_v6(gGeoIP[GEOIP_ASNUM_EDITION_V6], ip); - } - break; - } - if (asn_name) { - // This is a little odd, but the strings returned are e.g. "AS1234 Acme Inc" - while (*asn_name && !(isdigit(*asn_name))) { - ++asn_name; - } - ret = strtol(asn_name, nullptr, 10); - } - } - TSDebug(PLUGIN_NAME, "eval(): Client IPv%d seems to come from ASN #: %" PRId64, v, ret); - break; - - // Likely shouldn't trip, should we assert? - default: - break; - } - - return ret; -} - -#else - -// No Geo library available, these are just stubs. - -const char * +std::string ConditionGeo::get_geo_string(const sockaddr *addr) const { TSError("[%s] No Geo library available!", PLUGIN_NAME); - return nullptr; + return ""; } int64_t @@ -898,8 +756,6 @@ ConditionGeo::get_geo_int(const sockaddr *addr) const return 0; } -#endif - void ConditionGeo::initialize(Parser &p) { diff --git a/plugins/header_rewrite/conditions.h b/plugins/header_rewrite/conditions.h index ed6c61fc9c9..64edb722165 100644 --- a/plugins/header_rewrite/conditions.h +++ b/plugins/header_rewrite/conditions.h @@ -437,12 +437,12 @@ class ConditionGeo : public Condition _int_type = flag; } +private: + virtual int64_t get_geo_int(const sockaddr *addr) const; + virtual std::string get_geo_string(const sockaddr *addr) const; + protected: bool eval(const Resources &res) override; - -private: - int64_t get_geo_int(const sockaddr *addr) const; - const char *get_geo_string(const sockaddr *addr) const; GeoQualifiers _geo_qual = GEO_QUAL_COUNTRY; bool _int_type = false; }; diff --git a/plugins/header_rewrite/conditions_geo.h b/plugins/header_rewrite/conditions_geo.h new file mode 100644 index 00000000000..25477160ac2 --- /dev/null +++ b/plugins/header_rewrite/conditions_geo.h @@ -0,0 +1,46 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include +#include + +#include "conditions.h" + +class MMConditionGeo : public ConditionGeo +{ +public: + MMConditionGeo() {} + virtual ~MMConditionGeo() {} + + 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; +}; + +class GeoIPConditionGeo : public ConditionGeo +{ +public: + GeoIPConditionGeo() {} + virtual ~GeoIPConditionGeo() {} + + 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; +}; diff --git a/plugins/header_rewrite/conditions_geo_geoip.cc b/plugins/header_rewrite/conditions_geo_geoip.cc new file mode 100644 index 00000000000..f8830239444 --- /dev/null +++ b/plugins/header_rewrite/conditions_geo_geoip.cc @@ -0,0 +1,188 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +////////////////////////////////////////////////////////////////////////////////////////////// +// conditions_geo_geoip.cc: Implementation of the ConditionGeo class based on the GeoIP library +// +// + +#include +#include +#include + +#include "ts/ts.h" + +#include "conditions_geo.h" + +#include + +GeoIP *gGeoIP[NUM_DB_TYPES]; + +void +GeoIPConditionGeo::initLibrary(const std::string &) +{ + 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); + + char *db_info = GeoIP_database_info(gGeoIP[db]); + TSDebug(PLUGIN_NAME, "initialized GeoIP-DB[%d] %s", db, db_info); + free(db_info); + } + } +} + +std::string +GeoIPConditionGeo::get_geo_string(const sockaddr *addr) const +{ + std::string ret = "(unknown)"; + int v = 4; + + if (addr) { + switch (_geo_qual) { + // Country database + case GEO_QUAL_COUNTRY: + switch (addr->sa_family) { + case AF_INET: + if (gGeoIP[GEOIP_COUNTRY_EDITION]) { + uint32_t ip = ntohl(reinterpret_cast(addr)->sin_addr.s_addr); + + ret = GeoIP_country_code_by_ipnum(gGeoIP[GEOIP_COUNTRY_EDITION], ip); + } + break; + case AF_INET6: { + if (gGeoIP[GEOIP_COUNTRY_EDITION_V6]) { + geoipv6_t ip = reinterpret_cast(addr)->sin6_addr; + + v = 6; + ret = GeoIP_country_code_by_ipnum_v6(gGeoIP[GEOIP_COUNTRY_EDITION_V6], ip); + } + } break; + default: + break; + } + TSDebug(PLUGIN_NAME, "eval(): Client IPv%d seems to come from Country: %s", v, ret.c_str()); + break; + + // ASN database + case GEO_QUAL_ASN_NAME: + switch (addr->sa_family) { + case AF_INET: + if (gGeoIP[GEOIP_ASNUM_EDITION]) { + uint32_t ip = ntohl(reinterpret_cast(addr)->sin_addr.s_addr); + + ret = GeoIP_name_by_ipnum(gGeoIP[GEOIP_ASNUM_EDITION], ip); + } + break; + case AF_INET6: { + if (gGeoIP[GEOIP_ASNUM_EDITION_V6]) { + geoipv6_t ip = reinterpret_cast(addr)->sin6_addr; + + v = 6; + ret = GeoIP_name_by_ipnum_v6(gGeoIP[GEOIP_ASNUM_EDITION_V6], ip); + } + } break; + default: + break; + } + TSDebug(PLUGIN_NAME, "eval(): Client IPv%d seems to come from ASN Name: %s", v, ret.c_str()); + break; + default: + break; + } + } + + return ret; +} + +int64_t +GeoIPConditionGeo::get_geo_int(const sockaddr *addr) const +{ + int64_t ret = -1; + int v = 4; + + if (!addr) { + return 0; + } + + switch (_geo_qual) { + // Country Database + case GEO_QUAL_COUNTRY_ISO: + switch (addr->sa_family) { + case AF_INET: + if (gGeoIP[GEOIP_COUNTRY_EDITION]) { + uint32_t ip = ntohl(reinterpret_cast(addr)->sin_addr.s_addr); + + ret = GeoIP_id_by_ipnum(gGeoIP[GEOIP_COUNTRY_EDITION], ip); + } + break; + case AF_INET6: { + if (gGeoIP[GEOIP_COUNTRY_EDITION_V6]) { + geoipv6_t ip = reinterpret_cast(addr)->sin6_addr; + + v = 6; + ret = GeoIP_id_by_ipnum_v6(gGeoIP[GEOIP_COUNTRY_EDITION_V6], ip); + } + } break; + default: + break; + } + TSDebug(PLUGIN_NAME, "eval(): Client IPv%d seems to come from Country ISO: %" PRId64, v, ret); + break; + + case GEO_QUAL_ASN: { + const char *asn_name = nullptr; + + switch (addr->sa_family) { + case AF_INET: + if (gGeoIP[GEOIP_ASNUM_EDITION]) { + uint32_t ip = ntohl(reinterpret_cast(addr)->sin_addr.s_addr); + + asn_name = GeoIP_name_by_ipnum(gGeoIP[GEOIP_ASNUM_EDITION], ip); + } + break; + case AF_INET6: + if (gGeoIP[GEOIP_ASNUM_EDITION_V6]) { + geoipv6_t ip = reinterpret_cast(addr)->sin6_addr; + + v = 6; + asn_name = GeoIP_name_by_ipnum_v6(gGeoIP[GEOIP_ASNUM_EDITION_V6], ip); + } + break; + } + if (asn_name) { + // This is a little odd, but the strings returned are e.g. "AS1234 Acme Inc" + while (*asn_name && !(isdigit(*asn_name))) { + ++asn_name; + } + ret = strtol(asn_name, nullptr, 10); + } + } + TSDebug(PLUGIN_NAME, "eval(): Client IPv%d seems to come from ASN #: %" PRId64, v, ret); + break; + + // Likely shouldn't trip, should we assert? + default: + break; + } + + return ret; +} diff --git a/plugins/header_rewrite/conditions_geo_maxmind.cc b/plugins/header_rewrite/conditions_geo_maxmind.cc new file mode 100644 index 00000000000..8438e1c67a5 --- /dev/null +++ b/plugins/header_rewrite/conditions_geo_maxmind.cc @@ -0,0 +1,175 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +////////////////////////////////////////////////////////////////////////////////////////////// +// conditions_geo_maxmind.cc: Implementation of the ConditionGeo class based on MaxMindDB +// +// + +#include +#include + +#include "ts/ts.h" + +#include "conditions_geo.h" + +#include + +MMDB_s *gMaxMindDB = nullptr; + +void +MMConditionGeo::initLibrary(const std::string &path) +{ + if (path.empty()) { + TSError("[%s] Empty db path specified. Not initializing!", PLUGIN_NAME); + return; + } + gMaxMindDB = new MMDB_s; + + int status = MMDB_open(path.c_str(), MMDB_MODE_MMAP, gMaxMindDB); + if (MMDB_SUCCESS != status) { + TSDebug(PLUGIN_NAME, "Cannot open %s - %s", path.c_str(), MMDB_strerror(status)); + return; + } + TSDebug(PLUGIN_NAME, "Loaded %s", path.c_str()); +} + +std::string +MMConditionGeo::get_geo_string(const sockaddr *addr) const +{ + std::string ret = "(unknown)"; + int mmdb_error; + + if (gMaxMindDB == nullptr) { + return ret; + } + + MMDB_lookup_result_s result = MMDB_lookup_sockaddr(gMaxMindDB, addr, &mmdb_error); + + if (MMDB_SUCCESS != mmdb_error) { + TSDebug(PLUGIN_NAME, "Error during sockaddr lookup: %s", MMDB_strerror(mmdb_error)); + return ret; + } + + MMDB_entry_data_list_s *entry_data_list = nullptr; + if (!result.found_entry) { + TSDebug(PLUGIN_NAME, "No entry for this IP was found"); + return ret; + } + + int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list); + if (MMDB_SUCCESS != status) { + TSDebug(PLUGIN_NAME, "Error looking up entry data: %s", MMDB_strerror(status)); + return ret; + } + + if (entry_data_list == nullptr) { + TSDebug(PLUGIN_NAME, "No data found"); + return ret; + } + + const char *field_name; + switch (_geo_qual) { + case GEO_QUAL_COUNTRY: + field_name = "country_code"; + break; + case GEO_QUAL_ASN_NAME: + field_name = "autonomous_system_organization"; + break; + default: + TSDebug(PLUGIN_NAME, "Unsupported field %d", _geo_qual); + return ret; + break; + } + + MMDB_entry_data_s entry_data; + + status = MMDB_get_value(&result.entry, &entry_data, field_name, NULL); + if (MMDB_SUCCESS != status) { + TSDebug(PLUGIN_NAME, "ERROR on get value asn value: %s", MMDB_strerror(status)); + return ret; + } + ret = std::string(entry_data.utf8_string, entry_data.data_size); + + if (NULL != entry_data_list) { + MMDB_free_entry_data_list(entry_data_list); + } + + return ret; +} + +int64_t +MMConditionGeo::get_geo_int(const sockaddr *addr) const +{ + int64_t ret = -1; + int mmdb_error; + + if (gMaxMindDB == nullptr) { + return ret; + } + + MMDB_lookup_result_s result = MMDB_lookup_sockaddr(gMaxMindDB, addr, &mmdb_error); + + if (MMDB_SUCCESS != mmdb_error) { + TSDebug(PLUGIN_NAME, "Error during sockaddr lookup: %s", MMDB_strerror(mmdb_error)); + return ret; + } + + MMDB_entry_data_list_s *entry_data_list = nullptr; + if (!result.found_entry) { + TSDebug(PLUGIN_NAME, "No entry for this IP was found"); + return ret; + } + + int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list); + if (MMDB_SUCCESS != status) { + TSDebug(PLUGIN_NAME, "Error looking up entry data: %s", MMDB_strerror(status)); + return ret; + } + + if (entry_data_list == nullptr) { + TSDebug(PLUGIN_NAME, "No data found"); + return ret; + } + + const char *field_name; + switch (_geo_qual) { + case GEO_QUAL_ASN: + field_name = "autonomous_system"; + break; + default: + TSDebug(PLUGIN_NAME, "Unsupported field %d", _geo_qual); + return ret; + break; + } + + MMDB_entry_data_s entry_data; + + status = MMDB_get_value(&result.entry, &entry_data, field_name, NULL); + if (MMDB_SUCCESS != status) { + TSDebug(PLUGIN_NAME, "ERROR on get value asn value: %s", MMDB_strerror(status)); + return ret; + } + ret = entry_data.uint32; + + if (NULL != entry_data_list) { + MMDB_free_entry_data_list(entry_data_list); + } + + return ret; +} diff --git a/plugins/header_rewrite/factory.cc b/plugins/header_rewrite/factory.cc index 26f9a4f7454..961d17a5059 100644 --- a/plugins/header_rewrite/factory.cc +++ b/plugins/header_rewrite/factory.cc @@ -23,6 +23,7 @@ #include "operators.h" #include "conditions.h" +#include "conditions_geo.h" /////////////////////////////////////////////////////////////////////////////// // "Factory" functions, processing the parsed lines @@ -130,7 +131,13 @@ condition_factory(const std::string &cond) } else if (c_name == "NOW") { c = new ConditionNow(); } else if (c_name == "GEO") { +#if TS_USE_HRW_GEOIP + c = new GeoIPConditionGeo(); +#elif TS_USE_HRW_MAXMINDDB + c = new MMConditionGeo(); +#else c = new ConditionGeo(); +#endif } else if (c_name == "ID") { c = new ConditionId(); } else if (c_name == "CIDR") { diff --git a/plugins/header_rewrite/header_rewrite.cc b/plugins/header_rewrite/header_rewrite.cc index 85e0feebf72..55d8f0f38df 100644 --- a/plugins/header_rewrite/header_rewrite.cc +++ b/plugins/header_rewrite/header_rewrite.cc @@ -15,9 +15,12 @@ See the License for the specific language governing permissions and limitations under the License. */ + #include +#include #include #include +#include #include "ts/ts.h" #include "ts/remap.h" @@ -25,41 +28,24 @@ #include "parser.h" #include "ruleset.h" #include "resources.h" +#include "conditions.h" +#include "conditions_geo.h" // Debugs const char PLUGIN_NAME[] = "header_rewrite"; const char PLUGIN_NAME_DBG[] = "dbg_header_rewrite"; -// Geo information, currently only Maxmind. These have to be initialized when the plugin loads. -#if HAVE_GEOIP_H -#include - -GeoIP *gGeoIP[NUM_DB_TYPES]; +static std::once_flag initGeoLibs; static void -initGeoIP() +initGeoLib(const std::string &dbPath) { - 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); - - char *db_info = GeoIP_database_info(gGeoIP[db]); - TSDebug(PLUGIN_NAME, "initialized GeoIP-DB[%d] %s", db, db_info); - free(db_info); - } - } -} - -#else - -static void -initGeoIP() -{ -} +#if TS_USE_HRW_GEOIP + GeoIPConditionGeo::initLibrary(dbPath); +#elif TS_USE_HRW_MAXMINDDB + MMConditionGeo::initLibrary(dbPath); #endif +} // Forward declaration for the main continuation. static int cont_rewrite_headers(TSCont, TSEvent, void *); @@ -315,6 +301,8 @@ cont_rewrite_headers(TSCont contp, TSEvent event, void *edata) return 0; } +static const struct option longopt[] = {{"geo-db-path", required_argument, NULL, 'm'}, {NULL, no_argument, NULL, '\0'}}; + /////////////////////////////////////////////////////////////////////////////// // Initialize the InkAPI plugin for the global hooks we support. // @@ -329,6 +317,21 @@ TSPluginInit(int argc, const char *argv[]) if (TS_SUCCESS != TSPluginRegister(&info)) { TSError("[%s] plugin registration failed", PLUGIN_NAME); + return; + } + + std::string geoDBpath; + while (true) { + int opt = getopt_long(argc, (char *const *)argv, "m:", longopt, NULL); + + switch (opt) { + case 'm': { + geoDBpath = optarg; + } break; + } + if (opt == -1) { + break; + } } // Parse the global config file(s). All rules are just appended @@ -336,9 +339,9 @@ TSPluginInit(int argc, const char *argv[]) RulesConfig *conf = new RulesConfig; bool got_config = false; - initGeoIP(); + std::call_once(initGeoLibs, [&geoDBpath]() { initGeoLib(geoDBpath); }); - for (int i = 1; i < argc; ++i) { + for (int i = optind; i < argc; ++i) { // Parse the config file(s). Note that multiple config files are // just appended to the configurations. TSDebug(PLUGIN_NAME, "Loading global configuration file %s", argv[i]); @@ -389,7 +392,6 @@ TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) return TS_ERROR; } - initGeoIP(); TSDebug(PLUGIN_NAME, "Remap plugin is successfully initialized"); return TS_SUCCESS; @@ -405,9 +407,30 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char * /* errbuf ATS_UNUSE return TS_ERROR; } + // argv contains the "to" and "from" URLs. Skip the first so that the + // second one poses as the program name. + --argc; + ++argv; + + std::string geoDBPath; + while (true) { + int opt = getopt_long(argc, (char *const *)argv, "m:", longopt, NULL); + + switch (opt) { + case 'm': { + geoDBPath = optarg; + } break; + } + if (opt == -1) { + break; + } + } + + std::call_once(initGeoLibs, [&geoDBPath]() { initGeoLib(geoDBPath); }); + RulesConfig *conf = new RulesConfig; - for (int i = 2; i < argc; ++i) { + for (int i = optind; i < argc; ++i) { TSDebug(PLUGIN_NAME, "Loading remap configuration file %s", argv[i]); if (!conf->parse_config(argv[i], TS_REMAP_PSEUDO_HOOK)) { TSError("[%s] Unable to create remap instance", PLUGIN_NAME); diff --git a/plugins/header_rewrite/resources.h b/plugins/header_rewrite/resources.h index ee5e0f0e6ee..c3ddfa0a675 100644 --- a/plugins/header_rewrite/resources.h +++ b/plugins/header_rewrite/resources.h @@ -28,11 +28,6 @@ #include "lulu.h" -#if HAVE_GEOIP_H -#include -extern GeoIP *gGeoIP[NUM_DB_TYPES]; -#endif - enum ResourceIDs { RSRC_NONE = 0, RSRC_SERVER_RESPONSE_HEADERS = 1, diff --git a/plugins/healthchecks/Makefile.inc b/plugins/healthchecks/Makefile.inc index 2121b723176..f9c027c3da4 100644 --- a/plugins/healthchecks/Makefile.inc +++ b/plugins/healthchecks/Makefile.inc @@ -14,9 +14,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -if BUILD_HEALTHCHECK_PLUGIN - pkglib_LTLIBRARIES += healthchecks/healthchecks.la healthchecks_healthchecks_la_SOURCES = healthchecks/healthchecks.c - -endif