From c10d0c06cd9800aeeffb9bd122ff6bd7988caaf6 Mon Sep 17 00:00:00 2001 From: Leif Hedstrom Date: Tue, 26 May 2015 17:47:50 -0600 Subject: [PATCH] TS-3639 Implement GeoIP information into header_rewrite conditions and statements --- plugins/header_rewrite/Examples/Geo | 6 + plugins/header_rewrite/Makefile.am | 3 +- plugins/header_rewrite/conditions.cc | 214 +++++++++++++++++++++++ plugins/header_rewrite/conditions.h | 43 +++++ plugins/header_rewrite/factory.cc | 2 + plugins/header_rewrite/header_rewrite.cc | 34 ++++ plugins/header_rewrite/resources.cc | 1 + plugins/header_rewrite/resources.h | 4 + plugins/header_rewrite/statement.h | 8 + 9 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 plugins/header_rewrite/Examples/Geo diff --git a/plugins/header_rewrite/Examples/Geo b/plugins/header_rewrite/Examples/Geo new file mode 100644 index 00000000000..8213ee87ebe --- /dev/null +++ b/plugins/header_rewrite/Examples/Geo @@ -0,0 +1,6 @@ +cond %{SEND_RESPONSE_HDR_HOOK} [AND] +cond %${GEO:COUNTRY} =US + set-header ATS-Geo-Country %{GEO:COUNTRY} + set-header ATS-Geo-Country-ISO %{GEO:COUNTRY-ISO} + set-header ATS-Geo-ASN %{GEO:ASN} + set-header ATS-Geo-ASN-NAME %{GEO:ASN-NAME} diff --git a/plugins/header_rewrite/Makefile.am b/plugins/header_rewrite/Makefile.am index 5a7acd38bbe..c2f9776f41d 100644 --- a/plugins/header_rewrite/Makefile.am +++ b/plugins/header_rewrite/Makefile.am @@ -31,8 +31,9 @@ header_rewrite_la_SOURCES = \ resources.cc \ ruleset.cc \ statement.cc - + header_rewrite_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) +header_rewrite_la_LIBADD = $(GEOIP_LIBS) bin_PROGRAMS = header_rewrite_test header_rewrite_test_SOURCES = parser.cc header_rewrite_test.cc diff --git a/plugins/header_rewrite/conditions.cc b/plugins/header_rewrite/conditions.cc index 59a98c255ff..c8a000873cf 100644 --- a/plugins/header_rewrite/conditions.cc +++ b/plugins/header_rewrite/conditions.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "ts/ts.h" @@ -709,3 +710,216 @@ ConditionNow::eval(const Resources &res) return static_cast *>(_matcher)->test(now); } + + +// ConditionGeo: Geo-based information (integer). See ConditionGeoCountry for the string version. +const char * +ConditionGeo::get_geo_string(const sockaddr *addr) +{ + const char *ret = NULL; + int v = 4; + + 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) +{ + int64_t ret = -1; + int v = 4; + + switch (_geo_qual) { + // Country Databse + 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 = NULL; + + 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, NULL, 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; +} + +void +ConditionGeo::initialize(Parser &p) +{ + Condition::initialize(p); + + if (is_int_type()) { + Matchers *match = new Matchers(_cond_op); + + match->set(static_cast(strtol(p.get_arg().c_str(), NULL, 10))); + _matcher = match; + } else { + // The default is to have a string matcher + Matchers *match = new Matchers(_cond_op); + + match->set(p.get_arg()); + _matcher = match; + } +} + +void +ConditionGeo::set_qualifier(const std::string &q) +{ + Condition::set_qualifier(q); + + TSDebug(PLUGIN_NAME, "\tParsing %%{GEO:%s} qualifier", q.c_str()); + + if (q == "COUNTRY") { + _geo_qual = GEO_QUAL_COUNTRY; + is_int_type(false); + } else if (q == "COUNTRY-ISO") { + _geo_qual = GEO_QUAL_COUNTRY_ISO; + is_int_type(true); + } else if (q == "ASN") { + _geo_qual = GEO_QUAL_ASN; + is_int_type(true); + } else if (q == "ASN-NAME") { + _geo_qual = GEO_QUAL_ASN_NAME; + is_int_type(false); + } else { + TSError("[%s] Unknown Geo qualifier: %s", PLUGIN_NAME, q.c_str()); + } +} + +void +ConditionGeo::append_value(std::string &s, const Resources &res) +{ + std::ostringstream oss; + + if (is_int_type()) { + oss << get_geo_int(TSHttpTxnClientAddrGet(res.txnp)); + s += oss.str(); + TSDebug(PLUGIN_NAME, "Appending GEO() to evaluation value -> %s", s.c_str()); + } else { + oss << get_geo_string(TSHttpTxnClientAddrGet(res.txnp)); + s += oss.str(); + TSDebug(PLUGIN_NAME, "Appending GEO() to evaluation value -> %s", s.c_str()); + } +} + +bool +ConditionGeo::eval(const Resources &res) +{ + if (is_int_type()) { + int64_t geo = get_geo_int(TSHttpTxnClientAddrGet(res.txnp)); + + TSDebug(PLUGIN_NAME, "Evaluating GEO() -> %" PRId64, geo); + + return static_cast *>(_matcher)->test(geo); + } else { + std::string s; + + append_value(s, res); + bool rval = static_cast *>(_matcher)->test(s); + + TSDebug(PLUGIN_NAME, "Evaluating GEO(): %s - rval: %d", s.c_str(), rval); + return rval; + } +} diff --git a/plugins/header_rewrite/conditions.h b/plugins/header_rewrite/conditions.h index 5a77907552e..bb918a31a7b 100644 --- a/plugins/header_rewrite/conditions.h +++ b/plugins/header_rewrite/conditions.h @@ -220,6 +220,7 @@ class ConditionCookie : public Condition }; }; + // header class ConditionHeader : public Condition { @@ -241,6 +242,7 @@ class ConditionHeader : public Condition bool _client; }; + // path class ConditionPath : public Condition { @@ -374,6 +376,7 @@ class ConditionIncomingPort : public Condition DISALLOW_COPY_AND_ASSIGN(ConditionIncomingPort); }; + // Transact Count class ConditionTransactCount : public Condition { @@ -392,6 +395,7 @@ class ConditionTransactCount : public Condition DISALLOW_COPY_AND_ASSIGN(ConditionTransactCount); }; + // now: Keeping track of current time / day / hour etc. class ConditionNow : public Condition { @@ -411,4 +415,43 @@ class ConditionNow : public Condition }; +// GeoIP class for the "integer" based Geo information pieces +class ConditionGeo : public Condition +{ +public: + explicit ConditionGeo() : _geo_qual(GEO_QUAL_COUNTRY), _int_type(false) + { + TSDebug(PLUGIN_NAME_DBG, "Calling CTOR for ConditionGeo"); + }; + + void initialize(Parser &p); + void set_qualifier(const std::string &q); + void append_value(std::string &s, const Resources &res); + + // These are special for this sub-class + bool + is_int_type() const + { + return _int_type; + } + void + is_int_type(bool flag) + { + _int_type = flag; + } + + int64_t get_geo_int(const sockaddr *addr); + const char *get_geo_string(const sockaddr *addr); + +protected: + bool eval(const Resources &res); + +private: + DISALLOW_COPY_AND_ASSIGN(ConditionGeo); + + GeoQualifiers _geo_qual; + bool _int_type; +}; + + #endif // __CONDITIONS_H diff --git a/plugins/header_rewrite/factory.cc b/plugins/header_rewrite/factory.cc index 1c6a901930f..c4a2aa406d9 100644 --- a/plugins/header_rewrite/factory.cc +++ b/plugins/header_rewrite/factory.cc @@ -129,6 +129,8 @@ condition_factory(const std::string &cond) c = new ConditionTransactCount(); } else if (c_name == "NOW") { c = new ConditionNow(); + } else if (c_name == "GEO") { + c = new ConditionGeo(); } else { TSError("[%s] Unknown condition: %s", PLUGIN_NAME, c_name.c_str()); return NULL; diff --git a/plugins/header_rewrite/header_rewrite.cc b/plugins/header_rewrite/header_rewrite.cc index 3f2dea41015..7ebf5b45174 100644 --- a/plugins/header_rewrite/header_rewrite.cc +++ b/plugins/header_rewrite/header_rewrite.cc @@ -31,9 +31,40 @@ 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 void +initGeoIP() +{ + GeoIPDBTypes dbs[] = {GEOIP_COUNTRY_EDITION, GEOIP_COUNTRY_EDITION_V6, GEOIP_ASNUM_EDITION, GEOIP_ASNUM_EDITION_V6}; + + for (unsigned i = 0; i < sizeof(dbs) / sizeof(dbs[0]); ++i) { + if (!gGeoIP[dbs[i]] && GeoIP_db_avail(dbs[i])) { + // GEOIP_STANDARD seems to break threaded apps... + gGeoIP[dbs[i]] = GeoIP_open_type(dbs[i], GEOIP_MMAP_CACHE); + TSDebug(PLUGIN_NAME, "initialized GeoIP-DB[%d] %s", dbs[i], GeoIP_database_info(gGeoIP[dbs[i]])); + } + } +} + +#else + +static void +initGeoIP() +{ +} +#endif + + // Forward declaration for the main continuation. static int cont_rewrite_headers(TSCont, TSEvent, void *); + // Simple wrapper around a configuration file / set. This is useful such that // we can reuse most of the code for both global and per-remap rule sets. class RulesConfig @@ -304,6 +335,7 @@ TSPluginInit(int argc, const char *argv[]) RulesConfig *conf = new RulesConfig; bool got_config = false; + initGeoIP(); conf->hold(); for (int i = 1; i < argc; ++i) { @@ -358,7 +390,9 @@ TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) return TS_ERROR; } + initGeoIP(); TSDebug(PLUGIN_NAME, "Remap plugin is successfully initialized"); + return TS_SUCCESS; } diff --git a/plugins/header_rewrite/resources.cc b/plugins/header_rewrite/resources.cc index d934e4a0b52..2d40391d7cd 100644 --- a/plugins/header_rewrite/resources.cc +++ b/plugins/header_rewrite/resources.cc @@ -24,6 +24,7 @@ #include "resources.h" #include "lulu.h" + // Collect all resources void Resources::gather(const ResourceIDs ids, TSHttpHookID hook) diff --git a/plugins/header_rewrite/resources.h b/plugins/header_rewrite/resources.h index 69374ab6a47..8ad9cd7844a 100644 --- a/plugins/header_rewrite/resources.h +++ b/plugins/header_rewrite/resources.h @@ -29,6 +29,10 @@ #include "lulu.h" +#if HAVE_GEOIP_H +#include +extern GeoIP *gGeoIP[NUM_DB_TYPES]; +#endif #define TS_REMAP_PSEUDO_HOOK TS_HTTP_LAST_HOOK // Ugly, but use the "last hook" for remap instances. diff --git a/plugins/header_rewrite/statement.h b/plugins/header_rewrite/statement.h index 254cf55e249..a161b83e779 100644 --- a/plugins/header_rewrite/statement.h +++ b/plugins/header_rewrite/statement.h @@ -58,6 +58,13 @@ enum NowQualifiers { NOW_QUAL_YEARDAY }; +enum GeoQualifiers { + GEO_QUAL_COUNTRY, + GEO_QUAL_COUNTRY_ISO, + GEO_QUAL_ASN, + GEO_QUAL_ASN_NAME, +}; + class Statement { @@ -128,6 +135,7 @@ class Statement virtual void initialize_hooks(); UrlQualifiers parse_url_qualifier(const std::string &q) const; + NowQualifiers parse_now_qualifier(const std::string &q) const; int64_t get_now_qualified(NowQualifiers qual) const;