From: Sergey Kandaurov Date: Tue, 8 Sep 2026 19:56:49 +0000 (+0000) Subject: GeoIP: support for the MaxMind DB (MMDB) databases X-Git-Tag: release-1.31.7~7 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=2b5c2b605b5df669da5dec6749dcc76c07d1315d;p=nginx.git GeoIP: support for the MaxMind DB (MMDB) databases The MaxMind DB format is now recognized, with the fallback to the legacy format (reached End-of-Life in May 2022). Migration requires switching to MMDB database files: geoip_country /usr/share/GeoIP/GeoLite2-Country.mmdb; geoip_city /usr/share/GeoIP/GeoLite2-City.mmdb; geoip_org /usr/share/GeoIP/GeoLite2-ASN.mmdb; Either libmaxminddb or libGeoIP is sufficient to build the modules; configure fails if neither is found. Notes on the MMDB lookups: *) $geoip_country_code3 and $geoip_city_country_code3 are converted from the alpha-2 codes with a compiled-in ISO 3166-1 table, as the MMDB databases provide alpha-2 codes only. *) $geoip_org looks up "autonomous_system_organization", "isp", "organization", and "domain" in turn, to match the legacy ASNum, ISP, Org, and Domain database editions. *) $geoip_area_code has no MMDB counterpart and is never found. *) $geoip_region and $geoip_region_name come from subdivisions[0], and thus may differ in value from the legacy ones. *) $geoip_dma_code comes from location.metro_code, which is deprecated by MaxMind and may be absent. Co-authored-by: Nitin Swami --- diff --git a/auto/lib/geoip/conf b/auto/lib/geoip/conf index 47165b15b..9ca29f4d4 100644 --- a/auto/lib/geoip/conf +++ b/auto/lib/geoip/conf @@ -3,8 +3,11 @@ # Copyright (C) Nginx, Inc. +GEOIP=NO + + ngx_feature="GeoIP library" - ngx_feature_name= + ngx_feature_name=NGX_HAVE_GEOIP ngx_feature_run=no ngx_feature_incs="#include " ngx_feature_path= @@ -83,6 +86,8 @@ fi if [ $ngx_found = yes ]; then + GEOIP=YES + CORE_INCS="$CORE_INCS $ngx_feature_path" if [ $USE_GEOIP = YES ]; then @@ -101,11 +106,108 @@ if [ $ngx_found = yes ]; then ngx_feature_test="printf(\"%d\", GEOIP_CITY_EDITION_REV0_V6);" . auto/feature -else +fi + + + ngx_feature="MaxMind DB library" + ngx_feature_name=NGX_HAVE_MAXMINDDB + ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs="-lmaxminddb" + ngx_feature_test="MMDB_s mmdb; MMDB_open(NULL, MMDB_MODE_MMAP, &mmdb)" + . auto/feature + + +if [ $ngx_found = no ]; then + + # FreeBSD port + + ngx_feature="MaxMind DB library in /usr/local/" + ngx_feature_path="/usr/local/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lmaxminddb" + else + ngx_feature_libs="-L/usr/local/lib -lmaxminddb" + fi + + . auto/feature +fi + + +if [ $ngx_found = no ]; then + + # NetBSD port + + ngx_feature="MaxMind DB library in /usr/pkg/" + ngx_feature_path="/usr/pkg/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lmaxminddb" + else + ngx_feature_libs="-L/usr/pkg/lib -lmaxminddb" + fi + + . auto/feature +fi + + +if [ $ngx_found = no ]; then + + # MacPorts + + ngx_feature="MaxMind DB library in /opt/local/" + ngx_feature_path="/opt/local/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lmaxminddb" + else + ngx_feature_libs="-L/opt/local/lib -lmaxminddb" + fi + + . auto/feature +fi + + +if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="MaxMind DB library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lmaxminddb" + else + ngx_feature_libs="-L/opt/homebrew/lib -lmaxminddb" + fi + + . auto/feature +fi + + +if [ $ngx_found = yes ]; then + + GEOIP=YES + + CORE_INCS="$CORE_INCS $ngx_feature_path" + + if [ $USE_GEOIP = YES ]; then + CORE_LIBS="$CORE_LIBS $ngx_feature_libs" + fi + + NGX_LIB_GEOIP="$NGX_LIB_GEOIP $ngx_feature_libs" + +fi + + +if [ $GEOIP != YES ]; then cat << END -$0: error: the GeoIP module requires the GeoIP library. +$0: error: the GeoIP module requires the MaxMind DB library +or the legacy GeoIP library. You can either do not enable the module or install the library. END diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c index 5b8b11fc7..05f3d8aa3 100644 --- a/src/http/modules/ngx_http_geoip_module.c +++ b/src/http/modules/ngx_http_geoip_module.c @@ -9,19 +9,44 @@ #include #include +#if (NGX_HAVE_GEOIP) #include #include +#endif +#if (NGX_HAVE_MAXMINDDB) +#include +#endif #define NGX_GEOIP_COUNTRY_CODE 0 #define NGX_GEOIP_COUNTRY_CODE3 1 #define NGX_GEOIP_COUNTRY_NAME 2 +#define NGX_GEOIP_CITY_CONTINENT_CODE 0 +#define NGX_GEOIP_CITY_COUNTRY_CODE 1 +#define NGX_GEOIP_CITY_COUNTRY_CODE3 2 +#define NGX_GEOIP_CITY_COUNTRY_NAME 3 +#define NGX_GEOIP_CITY_REGION 4 +#define NGX_GEOIP_CITY_REGION_NAME 5 +#define NGX_GEOIP_CITY_NAME 6 +#define NGX_GEOIP_CITY_POSTAL_CODE 7 +#define NGX_GEOIP_CITY_LATITUDE 8 +#define NGX_GEOIP_CITY_LONGITUDE 9 +#define NGX_GEOIP_CITY_DMA_CODE 10 +#define NGX_GEOIP_CITY_AREA_CODE 11 + typedef struct { +#if (NGX_HAVE_GEOIP) GeoIP *country; GeoIP *org; GeoIP *city; +#endif +#if (NGX_HAVE_MAXMINDDB) + MMDB_s *country_mmdb; + MMDB_s *org_mmdb; + MMDB_s *city_mmdb; +#endif ngx_array_t *proxies; /* array of ngx_cidr_t */ ngx_flag_t proxy_recursive; #if (NGX_HAVE_GEOIP_V6) @@ -38,6 +63,18 @@ typedef struct { } ngx_http_geoip_var_t; +#if (NGX_HAVE_MAXMINDDB) + +typedef struct { + const char *alpha2; + const char *alpha3; +} ngx_http_geoip_ccode_t; + +#endif + + +#if (NGX_HAVE_GEOIP) + typedef const char *(*ngx_http_geoip_variable_handler_pt)(GeoIP *, u_long addr); @@ -63,6 +100,8 @@ ngx_http_geoip_variable_handler_v6_pt ngx_http_geoip_country_v6_functions[] = { #endif +#endif + static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); @@ -76,7 +115,20 @@ static ngx_int_t ngx_http_geoip_city_float_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_geoip_city_int_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +#if (NGX_HAVE_GEOIP) static GeoIPRecord *ngx_http_geoip_get_city_record(ngx_http_request_t *r); +#endif +#if (NGX_HAVE_MAXMINDDB) +static ngx_int_t ngx_http_geoip_str(ngx_http_request_t *r, MMDB_entry_s *entry, + const char **path, ngx_str_t *value); +static ngx_int_t ngx_http_geoip_code3(ngx_str_t *value); +static ngx_int_t ngx_http_geoip_float(MMDB_entry_s *entry, const char **path, + double *value); +static ngx_int_t ngx_http_geoip_int(MMDB_entry_s *entry, const char **path, + int64_t *value); +static ngx_int_t ngx_http_geoip_lookup(ngx_http_request_t *r, MMDB_s *mmdb, + MMDB_entry_s *entry); +#endif static ngx_int_t ngx_http_geoip_add_variables(ngx_conf_t *cf); static void *ngx_http_geoip_create_conf(ngx_conf_t *cf); @@ -87,6 +139,11 @@ static char *ngx_http_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +#if (NGX_HAVE_MAXMINDDB) +static ngx_int_t ngx_http_geoip_open(ngx_conf_t *cf, ngx_str_t *name, + MMDB_s **db); +static char *ngx_http_geoip_charset(ngx_conf_t *cf); +#endif static char *ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_geoip_cidr_value(ngx_conf_t *cf, ngx_str_t *net, @@ -186,56 +243,233 @@ static ngx_http_variable_t ngx_http_geoip_vars[] = { { ngx_string("geoip_city_continent_code"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, continent_code), 0, 0 }, + NGX_GEOIP_CITY_CONTINENT_CODE, 0, 0 }, { ngx_string("geoip_city_country_code"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, country_code), 0, 0 }, + NGX_GEOIP_CITY_COUNTRY_CODE, 0, 0 }, { ngx_string("geoip_city_country_code3"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, country_code3), 0, 0 }, + NGX_GEOIP_CITY_COUNTRY_CODE3, 0, 0 }, { ngx_string("geoip_city_country_name"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, country_name), 0, 0 }, + NGX_GEOIP_CITY_COUNTRY_NAME, 0, 0 }, { ngx_string("geoip_region"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, region), 0, 0 }, + NGX_GEOIP_CITY_REGION, 0, 0 }, { ngx_string("geoip_region_name"), NULL, ngx_http_geoip_region_name_variable, - 0, 0, 0 }, + NGX_GEOIP_CITY_REGION_NAME, 0, 0 }, { ngx_string("geoip_city"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, city), 0, 0 }, + NGX_GEOIP_CITY_NAME, 0, 0 }, { ngx_string("geoip_postal_code"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, postal_code), 0, 0 }, + NGX_GEOIP_CITY_POSTAL_CODE, 0, 0 }, { ngx_string("geoip_latitude"), NULL, ngx_http_geoip_city_float_variable, - offsetof(GeoIPRecord, latitude), 0, 0 }, + NGX_GEOIP_CITY_LATITUDE, 0, 0 }, { ngx_string("geoip_longitude"), NULL, ngx_http_geoip_city_float_variable, - offsetof(GeoIPRecord, longitude), 0, 0 }, + NGX_GEOIP_CITY_LONGITUDE, 0, 0 }, { ngx_string("geoip_dma_code"), NULL, ngx_http_geoip_city_int_variable, - offsetof(GeoIPRecord, dma_code), 0, 0 }, + NGX_GEOIP_CITY_DMA_CODE, 0, 0 }, { ngx_string("geoip_area_code"), NULL, ngx_http_geoip_city_int_variable, - offsetof(GeoIPRecord, area_code), 0, 0 }, + NGX_GEOIP_CITY_AREA_CODE, 0, 0 }, ngx_http_null_variable }; +#if (NGX_HAVE_GEOIP) + +/* indexed by NGX_GEOIP_CITY_* */ + +static size_t ngx_http_geoip_city_offsets[] = { + offsetof(GeoIPRecord, continent_code), + offsetof(GeoIPRecord, country_code), + offsetof(GeoIPRecord, country_code3), + offsetof(GeoIPRecord, country_name), + offsetof(GeoIPRecord, region), + 0, /* no region name */ + offsetof(GeoIPRecord, city), + offsetof(GeoIPRecord, postal_code), + offsetof(GeoIPRecord, latitude), + offsetof(GeoIPRecord, longitude), + offsetof(GeoIPRecord, dma_code), + offsetof(GeoIPRecord, area_code) +}; + +#endif + + +#if (NGX_HAVE_MAXMINDDB) + +static const char *ngx_http_geoip_path_country_code[] = { + "country", "iso_code", NULL +}; + +static const char *ngx_http_geoip_path_country_name[] = { + "country", "names", "en", NULL +}; + +static const char *ngx_http_geoip_path_continent_code[] = { + "continent", "code", NULL +}; + +static const char *ngx_http_geoip_path_region[] = { + "subdivisions", "0", "iso_code", NULL +}; + +static const char *ngx_http_geoip_path_region_name[] = { + "subdivisions", "0", "names", "en", NULL +}; + +static const char *ngx_http_geoip_path_city[] = { + "city", "names", "en", NULL +}; + +static const char *ngx_http_geoip_path_postal_code[] = { + "postal", "code", NULL +}; + +static const char *ngx_http_geoip_path_latitude[] = { + "location", "latitude", NULL +}; + +static const char *ngx_http_geoip_path_longitude[] = { + "location", "longitude", NULL +}; + +static const char *ngx_http_geoip_path_dma_code[] = { + "location", "metro_code", NULL +}; + + +/* indexed by NGX_GEOIP_COUNTRY_* */ + +static const char **ngx_http_geoip_country_paths[] = { + ngx_http_geoip_path_country_code, + ngx_http_geoip_path_country_code, /* converted to alpha-3 */ + ngx_http_geoip_path_country_name +}; + + +/* indexed by NGX_GEOIP_CITY_* */ + +static const char **ngx_http_geoip_city_paths[] = { + ngx_http_geoip_path_continent_code, + ngx_http_geoip_path_country_code, + ngx_http_geoip_path_country_code, /* converted to alpha-3 */ + ngx_http_geoip_path_country_name, + ngx_http_geoip_path_region, + ngx_http_geoip_path_region_name, + ngx_http_geoip_path_city, + ngx_http_geoip_path_postal_code, + ngx_http_geoip_path_latitude, + ngx_http_geoip_path_longitude, + ngx_http_geoip_path_dma_code, + NULL /* no area code */ +}; + + +/* ASNum, ISP, Org, and Domain editions, looked up in turn */ + +static const char *ngx_http_geoip_org_paths[][2] = { + { "autonomous_system_organization", NULL }, + { "isp", NULL }, + { "organization", NULL }, + { "domain", NULL }, + { NULL, NULL } +}; + + +/* MaxMind DB provides alpha-2 country codes only */ + +static ngx_http_geoip_ccode_t ngx_http_geoip_country_codes[] = { + { "AD", "AND" }, { "AE", "ARE" }, { "AF", "AFG" }, { "AG", "ATG" }, + { "AI", "AIA" }, { "AL", "ALB" }, { "AM", "ARM" }, { "AO", "AGO" }, + { "AQ", "ATA" }, { "AR", "ARG" }, { "AS", "ASM" }, { "AT", "AUT" }, + { "AU", "AUS" }, { "AW", "ABW" }, { "AX", "ALA" }, { "AZ", "AZE" }, + { "BA", "BIH" }, { "BB", "BRB" }, { "BD", "BGD" }, { "BE", "BEL" }, + { "BF", "BFA" }, { "BG", "BGR" }, { "BH", "BHR" }, { "BI", "BDI" }, + { "BJ", "BEN" }, { "BL", "BLM" }, { "BM", "BMU" }, { "BN", "BRN" }, + { "BO", "BOL" }, { "BQ", "BES" }, { "BR", "BRA" }, { "BS", "BHS" }, + { "BT", "BTN" }, { "BV", "BVT" }, { "BW", "BWA" }, { "BY", "BLR" }, + { "BZ", "BLZ" }, { "CA", "CAN" }, { "CC", "CCK" }, { "CD", "COD" }, + { "CF", "CAF" }, { "CG", "COG" }, { "CH", "CHE" }, { "CI", "CIV" }, + { "CK", "COK" }, { "CL", "CHL" }, { "CM", "CMR" }, { "CN", "CHN" }, + { "CO", "COL" }, { "CR", "CRI" }, { "CU", "CUB" }, { "CV", "CPV" }, + { "CW", "CUW" }, { "CX", "CXR" }, { "CY", "CYP" }, { "CZ", "CZE" }, + { "DE", "DEU" }, { "DJ", "DJI" }, { "DK", "DNK" }, { "DM", "DMA" }, + { "DO", "DOM" }, { "DZ", "DZA" }, { "EC", "ECU" }, { "EE", "EST" }, + { "EG", "EGY" }, { "EH", "ESH" }, { "ER", "ERI" }, { "ES", "ESP" }, + { "ET", "ETH" }, { "FI", "FIN" }, { "FJ", "FJI" }, { "FK", "FLK" }, + { "FM", "FSM" }, { "FO", "FRO" }, { "FR", "FRA" }, { "GA", "GAB" }, + { "GB", "GBR" }, { "GD", "GRD" }, { "GE", "GEO" }, { "GF", "GUF" }, + { "GG", "GGY" }, { "GH", "GHA" }, { "GI", "GIB" }, { "GL", "GRL" }, + { "GM", "GMB" }, { "GN", "GIN" }, { "GP", "GLP" }, { "GQ", "GNQ" }, + { "GR", "GRC" }, { "GS", "SGS" }, { "GT", "GTM" }, { "GU", "GUM" }, + { "GW", "GNB" }, { "GY", "GUY" }, { "HK", "HKG" }, { "HM", "HMD" }, + { "HN", "HND" }, { "HR", "HRV" }, { "HT", "HTI" }, { "HU", "HUN" }, + { "ID", "IDN" }, { "IE", "IRL" }, { "IL", "ISR" }, { "IM", "IMN" }, + { "IN", "IND" }, { "IO", "IOT" }, { "IQ", "IRQ" }, { "IR", "IRN" }, + { "IS", "ISL" }, { "IT", "ITA" }, { "JE", "JEY" }, { "JM", "JAM" }, + { "JO", "JOR" }, { "JP", "JPN" }, { "KE", "KEN" }, { "KG", "KGZ" }, + { "KH", "KHM" }, { "KI", "KIR" }, { "KM", "COM" }, { "KN", "KNA" }, + { "KP", "PRK" }, { "KR", "KOR" }, { "KW", "KWT" }, { "KY", "CYM" }, + { "KZ", "KAZ" }, { "LA", "LAO" }, { "LB", "LBN" }, { "LC", "LCA" }, + { "LI", "LIE" }, { "LK", "LKA" }, { "LR", "LBR" }, { "LS", "LSO" }, + { "LT", "LTU" }, { "LU", "LUX" }, { "LV", "LVA" }, { "LY", "LBY" }, + { "MA", "MAR" }, { "MC", "MCO" }, { "MD", "MDA" }, { "ME", "MNE" }, + { "MF", "MAF" }, { "MG", "MDG" }, { "MH", "MHL" }, { "MK", "MKD" }, + { "ML", "MLI" }, { "MM", "MMR" }, { "MN", "MNG" }, { "MO", "MAC" }, + { "MP", "MNP" }, { "MQ", "MTQ" }, { "MR", "MRT" }, { "MS", "MSR" }, + { "MT", "MLT" }, { "MU", "MUS" }, { "MV", "MDV" }, { "MW", "MWI" }, + { "MX", "MEX" }, { "MY", "MYS" }, { "MZ", "MOZ" }, { "NA", "NAM" }, + { "NC", "NCL" }, { "NE", "NER" }, { "NF", "NFK" }, { "NG", "NGA" }, + { "NI", "NIC" }, { "NL", "NLD" }, { "NO", "NOR" }, { "NP", "NPL" }, + { "NR", "NRU" }, { "NU", "NIU" }, { "NZ", "NZL" }, { "OM", "OMN" }, + { "PA", "PAN" }, { "PE", "PER" }, { "PF", "PYF" }, { "PG", "PNG" }, + { "PH", "PHL" }, { "PK", "PAK" }, { "PL", "POL" }, { "PM", "SPM" }, + { "PN", "PCN" }, { "PR", "PRI" }, { "PS", "PSE" }, { "PT", "PRT" }, + { "PW", "PLW" }, { "PY", "PRY" }, { "QA", "QAT" }, { "RE", "REU" }, + { "RO", "ROU" }, { "RS", "SRB" }, { "RU", "RUS" }, { "RW", "RWA" }, + { "SA", "SAU" }, { "SB", "SLB" }, { "SC", "SYC" }, { "SD", "SDN" }, + { "SE", "SWE" }, { "SG", "SGP" }, { "SH", "SHN" }, { "SI", "SVN" }, + { "SJ", "SJM" }, { "SK", "SVK" }, { "SL", "SLE" }, { "SM", "SMR" }, + { "SN", "SEN" }, { "SO", "SOM" }, { "SR", "SUR" }, { "SS", "SSD" }, + { "ST", "STP" }, { "SV", "SLV" }, { "SX", "SXM" }, { "SY", "SYR" }, + { "SZ", "SWZ" }, { "TC", "TCA" }, { "TD", "TCD" }, { "TF", "ATF" }, + { "TG", "TGO" }, { "TH", "THA" }, { "TJ", "TJK" }, { "TK", "TKL" }, + { "TL", "TLS" }, { "TM", "TKM" }, { "TN", "TUN" }, { "TO", "TON" }, + { "TR", "TUR" }, { "TT", "TTO" }, { "TV", "TUV" }, { "TW", "TWN" }, + { "TZ", "TZA" }, { "UA", "UKR" }, { "UG", "UGA" }, { "UM", "UMI" }, + { "US", "USA" }, { "UY", "URY" }, { "UZ", "UZB" }, { "VA", "VAT" }, + { "VC", "VCT" }, { "VE", "VEN" }, { "VG", "VGB" }, { "VI", "VIR" }, + { "VN", "VNM" }, { "VU", "VUT" }, { "WF", "WLF" }, { "WS", "WSM" }, + { "YE", "YEM" }, { "YT", "MYT" }, { "ZA", "ZAF" }, { "ZM", "ZMB" }, + { "ZW", "ZWE" }, + { NULL, NULL } +}; + +#endif + + +#if (NGX_HAVE_GEOIP) + static u_long ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) { @@ -336,11 +570,14 @@ ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) #endif +#endif + static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP) ngx_http_geoip_variable_handler_pt handler = ngx_http_geoip_country_functions[data]; #if (NGX_HAVE_GEOIP_V6) @@ -349,10 +586,52 @@ ngx_http_geoip_country_variable(ngx_http_request_t *r, #endif const char *val; +#endif +#if (NGX_HAVE_MAXMINDDB) + ngx_int_t rc; + ngx_str_t value; + MMDB_entry_s entry; +#endif ngx_http_geoip_conf_t *gcf; gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); +#if (NGX_HAVE_MAXMINDDB) + + if (gcf->country_mmdb) { + + if (ngx_http_geoip_lookup(r, gcf->country_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_http_geoip_str(r, &entry, + ngx_http_geoip_country_paths[data], &value); + + if (rc == NGX_OK && data == NGX_GEOIP_COUNTRY_CODE3) { + rc = ngx_http_geoip_code3(&value); + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_OK) { + goto not_found; + } + + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) + if (gcf->country == NULL) { goto not_found; } @@ -377,6 +656,8 @@ ngx_http_geoip_country_variable(ngx_http_request_t *r, return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -389,12 +670,55 @@ static ngx_int_t ngx_http_geoip_org_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP) size_t len; char *val; +#endif +#if (NGX_HAVE_MAXMINDDB) + ngx_int_t rc; + ngx_str_t value; + ngx_uint_t i; + MMDB_entry_s entry; +#endif ngx_http_geoip_conf_t *gcf; gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); +#if (NGX_HAVE_MAXMINDDB) + + if (gcf->org_mmdb) { + + if (ngx_http_geoip_lookup(r, gcf->org_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + for (i = 0; ngx_http_geoip_org_paths[i][0]; i++) { + + rc = ngx_http_geoip_str(r, &entry, + ngx_http_geoip_org_paths[i], &value); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; + } + } + + goto not_found; + } + +#endif + +#if (NGX_HAVE_GEOIP) + if (gcf->org == NULL) { goto not_found; } @@ -431,6 +755,8 @@ ngx_http_geoip_org_variable(ngx_http_request_t *r, return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -443,16 +769,59 @@ static ngx_int_t ngx_http_geoip_city_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - char *val; - size_t len; - GeoIPRecord *gr; +#if (NGX_HAVE_GEOIP) + char *val; + size_t len; + GeoIPRecord *gr; +#endif +#if (NGX_HAVE_MAXMINDDB) + ngx_int_t rc; + ngx_str_t value; + MMDB_entry_s entry; + ngx_http_geoip_conf_t *gcf; + + gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); + + if (gcf->city_mmdb) { + + if (ngx_http_geoip_lookup(r, gcf->city_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_http_geoip_str(r, &entry, + ngx_http_geoip_city_paths[data], &value); + + if (rc == NGX_OK && data == NGX_GEOIP_CITY_COUNTRY_CODE3) { + rc = ngx_http_geoip_code3(&value); + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_OK) { + goto not_found; + } + + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { goto not_found; } - val = *(char **) ((char *) gr + data); + val = *(char **) ((char *) gr + ngx_http_geoip_city_offsets[data]); if (val == NULL) { goto no_value; } @@ -479,6 +848,8 @@ no_value: GeoIPRecord_delete(gr); +#endif + not_found: v->not_found = 1; @@ -491,9 +862,48 @@ static ngx_int_t ngx_http_geoip_region_name_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - size_t len; - const char *val; - GeoIPRecord *gr; +#if (NGX_HAVE_GEOIP) + size_t len; + const char *val; + GeoIPRecord *gr; +#endif +#if (NGX_HAVE_MAXMINDDB) + ngx_int_t rc; + ngx_str_t value; + MMDB_entry_s entry; + ngx_http_geoip_conf_t *gcf; + + gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); + + if (gcf->city_mmdb) { + + if (ngx_http_geoip_lookup(r, gcf->city_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_http_geoip_str(r, &entry, + ngx_http_geoip_city_paths[data], &value); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_OK) { + goto not_found; + } + + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { @@ -523,6 +933,8 @@ ngx_http_geoip_region_name_variable(ngx_http_request_t *r, return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -535,13 +947,51 @@ static ngx_int_t ngx_http_geoip_city_float_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - float val; - GeoIPRecord *gr; +#if (NGX_HAVE_GEOIP) + float val; + GeoIPRecord *gr; +#endif +#if (NGX_HAVE_MAXMINDDB) + double value; + ngx_int_t rc; + MMDB_entry_s entry; + ngx_http_geoip_conf_t *gcf; + + gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); + + if (gcf->city_mmdb) { + + if (ngx_http_geoip_lookup(r, gcf->city_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_http_geoip_float(&entry, + ngx_http_geoip_city_paths[data], &value); + + if (rc != NGX_OK) { + goto not_found; + } + + v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN + 5); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(v->data, "%.4f", value) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { - v->not_found = 1; - return NGX_OK; + goto not_found; } v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN + 5); @@ -550,7 +1000,7 @@ ngx_http_geoip_city_float_variable(ngx_http_request_t *r, return NGX_ERROR; } - val = *(float *) ((char *) gr + data); + val = *(float *) ((char *) gr + ngx_http_geoip_city_offsets[data]); v->len = ngx_sprintf(v->data, "%.4f", val) - v->data; v->valid = 1; @@ -559,6 +1009,14 @@ ngx_http_geoip_city_float_variable(ngx_http_request_t *r, GeoIPRecord_delete(gr); + return NGX_OK; + +#endif + +not_found: + + v->not_found = 1; + return NGX_OK; } @@ -567,13 +1025,51 @@ static ngx_int_t ngx_http_geoip_city_int_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - int val; - GeoIPRecord *gr; +#if (NGX_HAVE_GEOIP) + int val; + GeoIPRecord *gr; +#endif +#if (NGX_HAVE_MAXMINDDB) + int64_t value; + ngx_int_t rc; + MMDB_entry_s entry; + ngx_http_geoip_conf_t *gcf; + + gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); + + if (gcf->city_mmdb) { + + if (ngx_http_geoip_lookup(r, gcf->city_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_http_geoip_int(&entry, + ngx_http_geoip_city_paths[data], &value); + + if (rc != NGX_OK) { + goto not_found; + } + + v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(v->data, "%L", value) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { - v->not_found = 1; - return NGX_OK; + goto not_found; } v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN); @@ -582,7 +1078,7 @@ ngx_http_geoip_city_int_variable(ngx_http_request_t *r, return NGX_ERROR; } - val = *(int *) ((char *) gr + data); + val = *(int *) ((char *) gr + ngx_http_geoip_city_offsets[data]); v->len = ngx_sprintf(v->data, "%d", val) - v->data; v->valid = 1; @@ -591,10 +1087,20 @@ ngx_http_geoip_city_int_variable(ngx_http_request_t *r, GeoIPRecord_delete(gr); + return NGX_OK; + +#endif + +not_found: + + v->not_found = 1; + return NGX_OK; } +#if (NGX_HAVE_GEOIP) + static GeoIPRecord * ngx_http_geoip_get_city_record(ngx_http_request_t *r) { @@ -617,6 +1123,194 @@ ngx_http_geoip_get_city_record(ngx_http_request_t *r) return NULL; } +#endif + + +#if (NGX_HAVE_MAXMINDDB) + +static ngx_int_t +ngx_http_geoip_str(ngx_http_request_t *r, MMDB_entry_s *entry, + const char **path, ngx_str_t *value) +{ + MMDB_entry_data_s entry_data; + + if (path == NULL + || MMDB_aget_value(entry, &entry_data, path) != MMDB_SUCCESS + || !entry_data.has_data + || entry_data.type != MMDB_DATA_TYPE_UTF8_STRING) + { + return NGX_DECLINED; + } + + value->data = ngx_pnalloc(r->pool, entry_data.data_size); + if (value->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(value->data, entry_data.utf8_string, entry_data.data_size); + value->len = entry_data.data_size; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_geoip_code3(ngx_str_t *value) +{ + ngx_http_geoip_ccode_t *cc; + + if (value->len != 2) { + return NGX_DECLINED; + } + + for (cc = ngx_http_geoip_country_codes; cc->alpha2; cc++) { + if (value->data[0] == cc->alpha2[0] + && value->data[1] == cc->alpha2[1]) + { + value->len = 3; + value->data = (u_char *) cc->alpha3; + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_geoip_float(MMDB_entry_s *entry, const char **path, double *value) +{ + MMDB_entry_data_s entry_data; + + if (path == NULL + || MMDB_aget_value(entry, &entry_data, path) != MMDB_SUCCESS + || !entry_data.has_data) + { + return NGX_DECLINED; + } + + switch (entry_data.type) { + + case MMDB_DATA_TYPE_DOUBLE: + *value = entry_data.double_value; + return NGX_OK; + + case MMDB_DATA_TYPE_FLOAT: + *value = entry_data.float_value; + return NGX_OK; + + default: + return NGX_DECLINED; + } +} + + +static ngx_int_t +ngx_http_geoip_int(MMDB_entry_s *entry, const char **path, int64_t *value) +{ + MMDB_entry_data_s entry_data; + + if (path == NULL + || MMDB_aget_value(entry, &entry_data, path) != MMDB_SUCCESS + || !entry_data.has_data) + { + return NGX_DECLINED; + } + + switch (entry_data.type) { + + case MMDB_DATA_TYPE_UINT16: + *value = entry_data.uint16; + return NGX_OK; + + case MMDB_DATA_TYPE_UINT32: + *value = entry_data.uint32; + return NGX_OK; + + case MMDB_DATA_TYPE_INT32: + *value = entry_data.int32; + return NGX_OK; + + default: + return NGX_DECLINED; + } +} + + +static ngx_int_t +ngx_http_geoip_lookup(ngx_http_request_t *r, MMDB_s *mmdb, MMDB_entry_s *entry) +{ + int err; + ngx_addr_t addr; + ngx_table_elt_t *xfwd; + MMDB_lookup_result_s result; + ngx_http_geoip_conf_t *gcf; +#if (NGX_HAVE_INET6) + struct in6_addr *inaddr6; + struct sockaddr_in sin; +#endif + + gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); + + addr.sockaddr = r->connection->sockaddr; + addr.socklen = r->connection->socklen; + /* addr.name = r->connection->addr_text; */ + + xfwd = r->headers_in.x_forwarded_for; + + if (xfwd != NULL && gcf->proxies != NULL) { + (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, + gcf->proxies, gcf->proxy_recursive); + } + + switch (addr.sockaddr->sa_family) { + + case AF_INET: + break; + +#if (NGX_HAVE_INET6) + case AF_INET6: + inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; + + if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { + ngx_memzero(&sin, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + ngx_memcpy(&sin.sin_addr, &inaddr6->s6_addr[12], 4); + + addr.sockaddr = (struct sockaddr *) &sin; + break; + } + + if (mmdb->metadata.ip_version == 4) { + return NGX_DECLINED; + } + + break; +#endif + + default: + return NGX_DECLINED; + } + + result = MMDB_lookup_sockaddr(mmdb, addr.sockaddr, &err); + + if (err != MMDB_SUCCESS) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "MMDB_lookup_sockaddr() failed: %s", MMDB_strerror(err)); + return NGX_DECLINED; + } + + if (!result.found_entry) { + return NGX_DECLINED; + } + + *entry = result.entry; + + return NGX_OK; +} + +#endif + static ngx_int_t ngx_http_geoip_add_variables(ngx_conf_t *cf) @@ -680,12 +1374,37 @@ ngx_http_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; +#if (NGX_HAVE_GEOIP) if (gcf->country) { return "is duplicate"; } +#endif +#if (NGX_HAVE_MAXMINDDB) + if (gcf->country_mmdb) { + return "is duplicate"; + } +#endif value = cf->args->elts; +#if (NGX_HAVE_MAXMINDDB) + + switch (ngx_http_geoip_open(cf, &value[1], &gcf->country_mmdb)) { + + case NGX_OK: + return ngx_http_geoip_charset(cf); + + case NGX_ERROR: + return NGX_CONF_ERROR; + + default: + break; + } + +#endif + +#if (NGX_HAVE_GEOIP) + gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->country == NULL) { @@ -725,6 +1444,15 @@ ngx_http_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) &value[1], gcf->country->databaseType); return NGX_CONF_ERROR; } + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" is not a MaxMind DB database", &value[1]); + + return NGX_CONF_ERROR; + +#endif } @@ -735,12 +1463,37 @@ ngx_http_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; +#if (NGX_HAVE_GEOIP) if (gcf->org) { return "is duplicate"; } +#endif +#if (NGX_HAVE_MAXMINDDB) + if (gcf->org_mmdb) { + return "is duplicate"; + } +#endif value = cf->args->elts; +#if (NGX_HAVE_MAXMINDDB) + + switch (ngx_http_geoip_open(cf, &value[1], &gcf->org_mmdb)) { + + case NGX_OK: + return ngx_http_geoip_charset(cf); + + case NGX_ERROR: + return NGX_CONF_ERROR; + + default: + break; + } + +#endif + +#if (NGX_HAVE_GEOIP) + gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->org == NULL) { @@ -786,6 +1539,15 @@ ngx_http_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) &value[1], gcf->org->databaseType); return NGX_CONF_ERROR; } + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" is not a MaxMind DB database", &value[1]); + + return NGX_CONF_ERROR; + +#endif } @@ -796,12 +1558,37 @@ ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; +#if (NGX_HAVE_GEOIP) if (gcf->city) { return "is duplicate"; } +#endif +#if (NGX_HAVE_MAXMINDDB) + if (gcf->city_mmdb) { + return "is duplicate"; + } +#endif value = cf->args->elts; +#if (NGX_HAVE_MAXMINDDB) + + switch (ngx_http_geoip_open(cf, &value[1], &gcf->city_mmdb)) { + + case NGX_OK: + return ngx_http_geoip_charset(cf); + + case NGX_ERROR: + return NGX_CONF_ERROR; + + default: + break; + } + +#endif + +#if (NGX_HAVE_GEOIP) + gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->city == NULL) { @@ -843,8 +1630,78 @@ ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) &value[1], gcf->city->databaseType); return NGX_CONF_ERROR; } + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" is not a MaxMind DB database", &value[1]); + + return NGX_CONF_ERROR; + +#endif +} + + +#if (NGX_HAVE_MAXMINDDB) + +static ngx_int_t +ngx_http_geoip_open(ngx_conf_t *cf, ngx_str_t *name, MMDB_s **db) +{ + int status; + MMDB_s *mmdb; + ngx_file_info_t fi; + + if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_file_info_n " \"%V\" failed", name); + return NGX_ERROR; + } + + mmdb = ngx_palloc(cf->pool, sizeof(MMDB_s)); + if (mmdb == NULL) { + return NGX_ERROR; + } + + status = MMDB_open((char *) name->data, MMDB_MODE_MMAP, mmdb); + + if (status == MMDB_SUCCESS) { + *db = mmdb; + return NGX_OK; + } + + /* presumably a legacy GeoIP database */ + + if (status == MMDB_INVALID_METADATA_ERROR) { + return NGX_DECLINED; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "MMDB_open(\"%V\") failed: %s", + name, MMDB_strerror(status)); + + return NGX_ERROR; +} + + +static char * +ngx_http_geoip_charset(ngx_conf_t *cf) +{ + ngx_str_t *value; + + value = cf->args->elts; + + /* MaxMind DB is always in UTF-8 */ + + if (cf->args->nelts == 3 && ngx_strcmp(value[2].data, "utf8") != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; } +#endif + static char * ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) @@ -911,6 +1768,8 @@ ngx_http_geoip_cleanup(void *data) { ngx_http_geoip_conf_t *gcf = data; +#if (NGX_HAVE_GEOIP) + if (gcf->country) { GeoIP_delete(gcf->country); } @@ -922,4 +1781,22 @@ ngx_http_geoip_cleanup(void *data) if (gcf->city) { GeoIP_delete(gcf->city); } + +#endif + +#if (NGX_HAVE_MAXMINDDB) + + if (gcf->country_mmdb) { + MMDB_close(gcf->country_mmdb); + } + + if (gcf->org_mmdb) { + MMDB_close(gcf->org_mmdb); + } + + if (gcf->city_mmdb) { + MMDB_close(gcf->city_mmdb); + } + +#endif } diff --git a/src/stream/ngx_stream_geoip_module.c b/src/stream/ngx_stream_geoip_module.c index 3ee8f0e33..67896837d 100644 --- a/src/stream/ngx_stream_geoip_module.c +++ b/src/stream/ngx_stream_geoip_module.c @@ -9,19 +9,44 @@ #include #include +#if (NGX_HAVE_GEOIP) #include #include +#endif +#if (NGX_HAVE_MAXMINDDB) +#include +#endif #define NGX_GEOIP_COUNTRY_CODE 0 #define NGX_GEOIP_COUNTRY_CODE3 1 #define NGX_GEOIP_COUNTRY_NAME 2 +#define NGX_GEOIP_CITY_CONTINENT_CODE 0 +#define NGX_GEOIP_CITY_COUNTRY_CODE 1 +#define NGX_GEOIP_CITY_COUNTRY_CODE3 2 +#define NGX_GEOIP_CITY_COUNTRY_NAME 3 +#define NGX_GEOIP_CITY_REGION 4 +#define NGX_GEOIP_CITY_REGION_NAME 5 +#define NGX_GEOIP_CITY_NAME 6 +#define NGX_GEOIP_CITY_POSTAL_CODE 7 +#define NGX_GEOIP_CITY_LATITUDE 8 +#define NGX_GEOIP_CITY_LONGITUDE 9 +#define NGX_GEOIP_CITY_DMA_CODE 10 +#define NGX_GEOIP_CITY_AREA_CODE 11 + typedef struct { +#if (NGX_HAVE_GEOIP) GeoIP *country; GeoIP *org; GeoIP *city; +#endif +#if (NGX_HAVE_MAXMINDDB) + MMDB_s *country_mmdb; + MMDB_s *org_mmdb; + MMDB_s *city_mmdb; +#endif #if (NGX_HAVE_GEOIP_V6) unsigned country_v6:1; unsigned org_v6:1; @@ -36,6 +61,18 @@ typedef struct { } ngx_stream_geoip_var_t; +#if (NGX_HAVE_MAXMINDDB) + +typedef struct { + const char *alpha2; + const char *alpha3; +} ngx_stream_geoip_ccode_t; + +#endif + + +#if (NGX_HAVE_GEOIP) + typedef const char *(*ngx_stream_geoip_variable_handler_pt)(GeoIP *, u_long addr); @@ -63,6 +100,8 @@ ngx_stream_geoip_variable_handler_v6_pt #endif +#endif + static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); @@ -76,7 +115,20 @@ static ngx_int_t ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +#if (NGX_HAVE_GEOIP) static GeoIPRecord *ngx_stream_geoip_get_city_record(ngx_stream_session_t *s); +#endif +#if (NGX_HAVE_MAXMINDDB) +static ngx_int_t ngx_stream_geoip_str(ngx_stream_session_t *s, + MMDB_entry_s *entry, const char **path, ngx_str_t *value); +static ngx_int_t ngx_stream_geoip_code3(ngx_str_t *value); +static ngx_int_t ngx_stream_geoip_float(MMDB_entry_s *entry, const char **path, + double *value); +static ngx_int_t ngx_stream_geoip_int(MMDB_entry_s *entry, const char **path, + int64_t *value); +static ngx_int_t ngx_stream_geoip_lookup(ngx_stream_session_t *s, MMDB_s *mmdb, + MMDB_entry_s *entry); +#endif static ngx_int_t ngx_stream_geoip_add_variables(ngx_conf_t *cf); static void *ngx_stream_geoip_create_conf(ngx_conf_t *cf); @@ -86,6 +138,11 @@ static char *ngx_stream_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +#if (NGX_HAVE_MAXMINDDB) +static ngx_int_t ngx_stream_geoip_open(ngx_conf_t *cf, ngx_str_t *name, + MMDB_s **db); +static char *ngx_stream_geoip_charset(ngx_conf_t *cf); +#endif static void ngx_stream_geoip_cleanup(void *data); @@ -164,56 +221,233 @@ static ngx_stream_variable_t ngx_stream_geoip_vars[] = { { ngx_string("geoip_city_continent_code"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, continent_code), 0, 0 }, + NGX_GEOIP_CITY_CONTINENT_CODE, 0, 0 }, { ngx_string("geoip_city_country_code"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, country_code), 0, 0 }, + NGX_GEOIP_CITY_COUNTRY_CODE, 0, 0 }, { ngx_string("geoip_city_country_code3"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, country_code3), 0, 0 }, + NGX_GEOIP_CITY_COUNTRY_CODE3, 0, 0 }, { ngx_string("geoip_city_country_name"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, country_name), 0, 0 }, + NGX_GEOIP_CITY_COUNTRY_NAME, 0, 0 }, { ngx_string("geoip_region"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, region), 0, 0 }, + NGX_GEOIP_CITY_REGION, 0, 0 }, { ngx_string("geoip_region_name"), NULL, ngx_stream_geoip_region_name_variable, - 0, 0, 0 }, + NGX_GEOIP_CITY_REGION_NAME, 0, 0 }, { ngx_string("geoip_city"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, city), 0, 0 }, + NGX_GEOIP_CITY_NAME, 0, 0 }, { ngx_string("geoip_postal_code"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, postal_code), 0, 0 }, + NGX_GEOIP_CITY_POSTAL_CODE, 0, 0 }, { ngx_string("geoip_latitude"), NULL, ngx_stream_geoip_city_float_variable, - offsetof(GeoIPRecord, latitude), 0, 0 }, + NGX_GEOIP_CITY_LATITUDE, 0, 0 }, { ngx_string("geoip_longitude"), NULL, ngx_stream_geoip_city_float_variable, - offsetof(GeoIPRecord, longitude), 0, 0 }, + NGX_GEOIP_CITY_LONGITUDE, 0, 0 }, { ngx_string("geoip_dma_code"), NULL, ngx_stream_geoip_city_int_variable, - offsetof(GeoIPRecord, dma_code), 0, 0 }, + NGX_GEOIP_CITY_DMA_CODE, 0, 0 }, { ngx_string("geoip_area_code"), NULL, ngx_stream_geoip_city_int_variable, - offsetof(GeoIPRecord, area_code), 0, 0 }, + NGX_GEOIP_CITY_AREA_CODE, 0, 0 }, ngx_stream_null_variable }; +#if (NGX_HAVE_GEOIP) + +/* indexed by NGX_GEOIP_CITY_* */ + +static size_t ngx_stream_geoip_city_offsets[] = { + offsetof(GeoIPRecord, continent_code), + offsetof(GeoIPRecord, country_code), + offsetof(GeoIPRecord, country_code3), + offsetof(GeoIPRecord, country_name), + offsetof(GeoIPRecord, region), + 0, /* no region name */ + offsetof(GeoIPRecord, city), + offsetof(GeoIPRecord, postal_code), + offsetof(GeoIPRecord, latitude), + offsetof(GeoIPRecord, longitude), + offsetof(GeoIPRecord, dma_code), + offsetof(GeoIPRecord, area_code) +}; + +#endif + + +#if (NGX_HAVE_MAXMINDDB) + +static const char *ngx_stream_geoip_path_country_code[] = { + "country", "iso_code", NULL +}; + +static const char *ngx_stream_geoip_path_country_name[] = { + "country", "names", "en", NULL +}; + +static const char *ngx_stream_geoip_path_continent_code[] = { + "continent", "code", NULL +}; + +static const char *ngx_stream_geoip_path_region[] = { + "subdivisions", "0", "iso_code", NULL +}; + +static const char *ngx_stream_geoip_path_region_name[] = { + "subdivisions", "0", "names", "en", NULL +}; + +static const char *ngx_stream_geoip_path_city[] = { + "city", "names", "en", NULL +}; + +static const char *ngx_stream_geoip_path_postal_code[] = { + "postal", "code", NULL +}; + +static const char *ngx_stream_geoip_path_latitude[] = { + "location", "latitude", NULL +}; + +static const char *ngx_stream_geoip_path_longitude[] = { + "location", "longitude", NULL +}; + +static const char *ngx_stream_geoip_path_dma_code[] = { + "location", "metro_code", NULL +}; + + +/* indexed by NGX_GEOIP_COUNTRY_* */ + +static const char **ngx_stream_geoip_country_paths[] = { + ngx_stream_geoip_path_country_code, + ngx_stream_geoip_path_country_code, /* converted to alpha-3 */ + ngx_stream_geoip_path_country_name +}; + + +/* indexed by NGX_GEOIP_CITY_* */ + +static const char **ngx_stream_geoip_city_paths[] = { + ngx_stream_geoip_path_continent_code, + ngx_stream_geoip_path_country_code, + ngx_stream_geoip_path_country_code, /* converted to alpha-3 */ + ngx_stream_geoip_path_country_name, + ngx_stream_geoip_path_region, + ngx_stream_geoip_path_region_name, + ngx_stream_geoip_path_city, + ngx_stream_geoip_path_postal_code, + ngx_stream_geoip_path_latitude, + ngx_stream_geoip_path_longitude, + ngx_stream_geoip_path_dma_code, + NULL /* no area code */ +}; + + +/* ASNum, ISP, Org, and Domain editions, looked up in turn */ + +static const char *ngx_stream_geoip_org_paths[][2] = { + { "autonomous_system_organization", NULL }, + { "isp", NULL }, + { "organization", NULL }, + { "domain", NULL }, + { NULL, NULL } +}; + + +/* MaxMind DB provides alpha-2 country codes only */ + +static ngx_stream_geoip_ccode_t ngx_stream_geoip_country_codes[] = { + { "AD", "AND" }, { "AE", "ARE" }, { "AF", "AFG" }, { "AG", "ATG" }, + { "AI", "AIA" }, { "AL", "ALB" }, { "AM", "ARM" }, { "AO", "AGO" }, + { "AQ", "ATA" }, { "AR", "ARG" }, { "AS", "ASM" }, { "AT", "AUT" }, + { "AU", "AUS" }, { "AW", "ABW" }, { "AX", "ALA" }, { "AZ", "AZE" }, + { "BA", "BIH" }, { "BB", "BRB" }, { "BD", "BGD" }, { "BE", "BEL" }, + { "BF", "BFA" }, { "BG", "BGR" }, { "BH", "BHR" }, { "BI", "BDI" }, + { "BJ", "BEN" }, { "BL", "BLM" }, { "BM", "BMU" }, { "BN", "BRN" }, + { "BO", "BOL" }, { "BQ", "BES" }, { "BR", "BRA" }, { "BS", "BHS" }, + { "BT", "BTN" }, { "BV", "BVT" }, { "BW", "BWA" }, { "BY", "BLR" }, + { "BZ", "BLZ" }, { "CA", "CAN" }, { "CC", "CCK" }, { "CD", "COD" }, + { "CF", "CAF" }, { "CG", "COG" }, { "CH", "CHE" }, { "CI", "CIV" }, + { "CK", "COK" }, { "CL", "CHL" }, { "CM", "CMR" }, { "CN", "CHN" }, + { "CO", "COL" }, { "CR", "CRI" }, { "CU", "CUB" }, { "CV", "CPV" }, + { "CW", "CUW" }, { "CX", "CXR" }, { "CY", "CYP" }, { "CZ", "CZE" }, + { "DE", "DEU" }, { "DJ", "DJI" }, { "DK", "DNK" }, { "DM", "DMA" }, + { "DO", "DOM" }, { "DZ", "DZA" }, { "EC", "ECU" }, { "EE", "EST" }, + { "EG", "EGY" }, { "EH", "ESH" }, { "ER", "ERI" }, { "ES", "ESP" }, + { "ET", "ETH" }, { "FI", "FIN" }, { "FJ", "FJI" }, { "FK", "FLK" }, + { "FM", "FSM" }, { "FO", "FRO" }, { "FR", "FRA" }, { "GA", "GAB" }, + { "GB", "GBR" }, { "GD", "GRD" }, { "GE", "GEO" }, { "GF", "GUF" }, + { "GG", "GGY" }, { "GH", "GHA" }, { "GI", "GIB" }, { "GL", "GRL" }, + { "GM", "GMB" }, { "GN", "GIN" }, { "GP", "GLP" }, { "GQ", "GNQ" }, + { "GR", "GRC" }, { "GS", "SGS" }, { "GT", "GTM" }, { "GU", "GUM" }, + { "GW", "GNB" }, { "GY", "GUY" }, { "HK", "HKG" }, { "HM", "HMD" }, + { "HN", "HND" }, { "HR", "HRV" }, { "HT", "HTI" }, { "HU", "HUN" }, + { "ID", "IDN" }, { "IE", "IRL" }, { "IL", "ISR" }, { "IM", "IMN" }, + { "IN", "IND" }, { "IO", "IOT" }, { "IQ", "IRQ" }, { "IR", "IRN" }, + { "IS", "ISL" }, { "IT", "ITA" }, { "JE", "JEY" }, { "JM", "JAM" }, + { "JO", "JOR" }, { "JP", "JPN" }, { "KE", "KEN" }, { "KG", "KGZ" }, + { "KH", "KHM" }, { "KI", "KIR" }, { "KM", "COM" }, { "KN", "KNA" }, + { "KP", "PRK" }, { "KR", "KOR" }, { "KW", "KWT" }, { "KY", "CYM" }, + { "KZ", "KAZ" }, { "LA", "LAO" }, { "LB", "LBN" }, { "LC", "LCA" }, + { "LI", "LIE" }, { "LK", "LKA" }, { "LR", "LBR" }, { "LS", "LSO" }, + { "LT", "LTU" }, { "LU", "LUX" }, { "LV", "LVA" }, { "LY", "LBY" }, + { "MA", "MAR" }, { "MC", "MCO" }, { "MD", "MDA" }, { "ME", "MNE" }, + { "MF", "MAF" }, { "MG", "MDG" }, { "MH", "MHL" }, { "MK", "MKD" }, + { "ML", "MLI" }, { "MM", "MMR" }, { "MN", "MNG" }, { "MO", "MAC" }, + { "MP", "MNP" }, { "MQ", "MTQ" }, { "MR", "MRT" }, { "MS", "MSR" }, + { "MT", "MLT" }, { "MU", "MUS" }, { "MV", "MDV" }, { "MW", "MWI" }, + { "MX", "MEX" }, { "MY", "MYS" }, { "MZ", "MOZ" }, { "NA", "NAM" }, + { "NC", "NCL" }, { "NE", "NER" }, { "NF", "NFK" }, { "NG", "NGA" }, + { "NI", "NIC" }, { "NL", "NLD" }, { "NO", "NOR" }, { "NP", "NPL" }, + { "NR", "NRU" }, { "NU", "NIU" }, { "NZ", "NZL" }, { "OM", "OMN" }, + { "PA", "PAN" }, { "PE", "PER" }, { "PF", "PYF" }, { "PG", "PNG" }, + { "PH", "PHL" }, { "PK", "PAK" }, { "PL", "POL" }, { "PM", "SPM" }, + { "PN", "PCN" }, { "PR", "PRI" }, { "PS", "PSE" }, { "PT", "PRT" }, + { "PW", "PLW" }, { "PY", "PRY" }, { "QA", "QAT" }, { "RE", "REU" }, + { "RO", "ROU" }, { "RS", "SRB" }, { "RU", "RUS" }, { "RW", "RWA" }, + { "SA", "SAU" }, { "SB", "SLB" }, { "SC", "SYC" }, { "SD", "SDN" }, + { "SE", "SWE" }, { "SG", "SGP" }, { "SH", "SHN" }, { "SI", "SVN" }, + { "SJ", "SJM" }, { "SK", "SVK" }, { "SL", "SLE" }, { "SM", "SMR" }, + { "SN", "SEN" }, { "SO", "SOM" }, { "SR", "SUR" }, { "SS", "SSD" }, + { "ST", "STP" }, { "SV", "SLV" }, { "SX", "SXM" }, { "SY", "SYR" }, + { "SZ", "SWZ" }, { "TC", "TCA" }, { "TD", "TCD" }, { "TF", "ATF" }, + { "TG", "TGO" }, { "TH", "THA" }, { "TJ", "TJK" }, { "TK", "TKL" }, + { "TL", "TLS" }, { "TM", "TKM" }, { "TN", "TUN" }, { "TO", "TON" }, + { "TR", "TUR" }, { "TT", "TTO" }, { "TV", "TUV" }, { "TW", "TWN" }, + { "TZ", "TZA" }, { "UA", "UKR" }, { "UG", "UGA" }, { "UM", "UMI" }, + { "US", "USA" }, { "UY", "URY" }, { "UZ", "UZB" }, { "VA", "VAT" }, + { "VC", "VCT" }, { "VE", "VEN" }, { "VG", "VGB" }, { "VI", "VIR" }, + { "VN", "VNM" }, { "VU", "VUT" }, { "WF", "WLF" }, { "WS", "WSM" }, + { "YE", "YEM" }, { "YT", "MYT" }, { "ZA", "ZAF" }, { "ZM", "ZMB" }, + { "ZW", "ZWE" }, + { NULL, NULL } +}; + +#endif + + +#if (NGX_HAVE_GEOIP) + static u_long ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) { @@ -298,11 +532,14 @@ ngx_stream_geoip_addr_v6(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) #endif +#endif + static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP) ngx_stream_geoip_variable_handler_pt handler = ngx_stream_geoip_country_functions[data]; #if (NGX_HAVE_GEOIP_V6) @@ -311,10 +548,52 @@ ngx_stream_geoip_country_variable(ngx_stream_session_t *s, #endif const char *val; +#endif +#if (NGX_HAVE_MAXMINDDB) + ngx_int_t rc; + ngx_str_t value; + MMDB_entry_s entry; +#endif ngx_stream_geoip_conf_t *gcf; gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); +#if (NGX_HAVE_MAXMINDDB) + + if (gcf->country_mmdb) { + + if (ngx_stream_geoip_lookup(s, gcf->country_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_stream_geoip_str(s, &entry, + ngx_stream_geoip_country_paths[data], &value); + + if (rc == NGX_OK && data == NGX_GEOIP_COUNTRY_CODE3) { + rc = ngx_stream_geoip_code3(&value); + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_OK) { + goto not_found; + } + + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) + if (gcf->country == NULL) { goto not_found; } @@ -339,6 +618,8 @@ ngx_stream_geoip_country_variable(ngx_stream_session_t *s, return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -351,12 +632,55 @@ static ngx_int_t ngx_stream_geoip_org_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP) size_t len; char *val; +#endif +#if (NGX_HAVE_MAXMINDDB) + ngx_int_t rc; + ngx_str_t value; + ngx_uint_t i; + MMDB_entry_s entry; +#endif ngx_stream_geoip_conf_t *gcf; gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); +#if (NGX_HAVE_MAXMINDDB) + + if (gcf->org_mmdb) { + + if (ngx_stream_geoip_lookup(s, gcf->org_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + for (i = 0; ngx_stream_geoip_org_paths[i][0]; i++) { + + rc = ngx_stream_geoip_str(s, &entry, + ngx_stream_geoip_org_paths[i], &value); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; + } + } + + goto not_found; + } + +#endif + +#if (NGX_HAVE_GEOIP) + if (gcf->org == NULL) { goto not_found; } @@ -393,6 +717,8 @@ ngx_stream_geoip_org_variable(ngx_stream_session_t *s, return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -405,16 +731,59 @@ static ngx_int_t ngx_stream_geoip_city_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { - char *val; - size_t len; - GeoIPRecord *gr; +#if (NGX_HAVE_GEOIP) + char *val; + size_t len; + GeoIPRecord *gr; +#endif +#if (NGX_HAVE_MAXMINDDB) + ngx_int_t rc; + ngx_str_t value; + MMDB_entry_s entry; + ngx_stream_geoip_conf_t *gcf; + + gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); + + if (gcf->city_mmdb) { + + if (ngx_stream_geoip_lookup(s, gcf->city_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_stream_geoip_str(s, &entry, + ngx_stream_geoip_city_paths[data], &value); + + if (rc == NGX_OK && data == NGX_GEOIP_CITY_COUNTRY_CODE3) { + rc = ngx_stream_geoip_code3(&value); + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_OK) { + goto not_found; + } + + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) gr = ngx_stream_geoip_get_city_record(s); if (gr == NULL) { goto not_found; } - val = *(char **) ((char *) gr + data); + val = *(char **) ((char *) gr + ngx_stream_geoip_city_offsets[data]); if (val == NULL) { goto no_value; } @@ -441,6 +810,8 @@ no_value: GeoIPRecord_delete(gr); +#endif + not_found: v->not_found = 1; @@ -453,9 +824,48 @@ static ngx_int_t ngx_stream_geoip_region_name_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { - size_t len; - const char *val; - GeoIPRecord *gr; +#if (NGX_HAVE_GEOIP) + size_t len; + const char *val; + GeoIPRecord *gr; +#endif +#if (NGX_HAVE_MAXMINDDB) + ngx_int_t rc; + ngx_str_t value; + MMDB_entry_s entry; + ngx_stream_geoip_conf_t *gcf; + + gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); + + if (gcf->city_mmdb) { + + if (ngx_stream_geoip_lookup(s, gcf->city_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_stream_geoip_str(s, &entry, + ngx_stream_geoip_city_paths[data], &value); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_OK) { + goto not_found; + } + + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) gr = ngx_stream_geoip_get_city_record(s); if (gr == NULL) { @@ -485,6 +895,8 @@ ngx_stream_geoip_region_name_variable(ngx_stream_session_t *s, return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -497,13 +909,51 @@ static ngx_int_t ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { - float val; - GeoIPRecord *gr; +#if (NGX_HAVE_GEOIP) + float val; + GeoIPRecord *gr; +#endif +#if (NGX_HAVE_MAXMINDDB) + double value; + ngx_int_t rc; + MMDB_entry_s entry; + ngx_stream_geoip_conf_t *gcf; + + gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); + + if (gcf->city_mmdb) { + + if (ngx_stream_geoip_lookup(s, gcf->city_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_stream_geoip_float(&entry, + ngx_stream_geoip_city_paths[data], &value); + + if (rc != NGX_OK) { + goto not_found; + } + + v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN + 5); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(v->data, "%.4f", value) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) gr = ngx_stream_geoip_get_city_record(s); if (gr == NULL) { - v->not_found = 1; - return NGX_OK; + goto not_found; } v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN + 5); @@ -512,7 +962,7 @@ ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s, return NGX_ERROR; } - val = *(float *) ((char *) gr + data); + val = *(float *) ((char *) gr + ngx_stream_geoip_city_offsets[data]); v->len = ngx_sprintf(v->data, "%.4f", val) - v->data; v->valid = 1; @@ -521,6 +971,14 @@ ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s, GeoIPRecord_delete(gr); + return NGX_OK; + +#endif + +not_found: + + v->not_found = 1; + return NGX_OK; } @@ -529,13 +987,51 @@ static ngx_int_t ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { - int val; - GeoIPRecord *gr; +#if (NGX_HAVE_GEOIP) + int val; + GeoIPRecord *gr; +#endif +#if (NGX_HAVE_MAXMINDDB) + int64_t value; + ngx_int_t rc; + MMDB_entry_s entry; + ngx_stream_geoip_conf_t *gcf; + + gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); + + if (gcf->city_mmdb) { + + if (ngx_stream_geoip_lookup(s, gcf->city_mmdb, &entry) != NGX_OK) { + goto not_found; + } + + rc = ngx_stream_geoip_int(&entry, + ngx_stream_geoip_city_paths[data], &value); + + if (rc != NGX_OK) { + goto not_found; + } + + v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(v->data, "%L", value) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_GEOIP) gr = ngx_stream_geoip_get_city_record(s); if (gr == NULL) { - v->not_found = 1; - return NGX_OK; + goto not_found; } v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN); @@ -544,7 +1040,7 @@ ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s, return NGX_ERROR; } - val = *(int *) ((char *) gr + data); + val = *(int *) ((char *) gr + ngx_stream_geoip_city_offsets[data]); v->len = ngx_sprintf(v->data, "%d", val) - v->data; v->valid = 1; @@ -553,10 +1049,20 @@ ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s, GeoIPRecord_delete(gr); + return NGX_OK; + +#endif + +not_found: + + v->not_found = 1; + return NGX_OK; } +#if (NGX_HAVE_GEOIP) + static GeoIPRecord * ngx_stream_geoip_get_city_record(ngx_stream_session_t *s) { @@ -579,6 +1085,182 @@ ngx_stream_geoip_get_city_record(ngx_stream_session_t *s) return NULL; } +#endif + + +#if (NGX_HAVE_MAXMINDDB) + +static ngx_int_t +ngx_stream_geoip_str(ngx_stream_session_t *s, MMDB_entry_s *entry, + const char **path, ngx_str_t *value) +{ + MMDB_entry_data_s entry_data; + + if (path == NULL + || MMDB_aget_value(entry, &entry_data, path) != MMDB_SUCCESS + || !entry_data.has_data + || entry_data.type != MMDB_DATA_TYPE_UTF8_STRING) + { + return NGX_DECLINED; + } + + value->data = ngx_pnalloc(s->connection->pool, entry_data.data_size); + if (value->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(value->data, entry_data.utf8_string, entry_data.data_size); + value->len = entry_data.data_size; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_geoip_code3(ngx_str_t *value) +{ + ngx_stream_geoip_ccode_t *cc; + + if (value->len != 2) { + return NGX_DECLINED; + } + + for (cc = ngx_stream_geoip_country_codes; cc->alpha2; cc++) { + if (value->data[0] == cc->alpha2[0] + && value->data[1] == cc->alpha2[1]) + { + value->len = 3; + value->data = (u_char *) cc->alpha3; + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_stream_geoip_float(MMDB_entry_s *entry, const char **path, double *value) +{ + MMDB_entry_data_s entry_data; + + if (path == NULL + || MMDB_aget_value(entry, &entry_data, path) != MMDB_SUCCESS + || !entry_data.has_data) + { + return NGX_DECLINED; + } + + switch (entry_data.type) { + + case MMDB_DATA_TYPE_DOUBLE: + *value = entry_data.double_value; + return NGX_OK; + + case MMDB_DATA_TYPE_FLOAT: + *value = entry_data.float_value; + return NGX_OK; + + default: + return NGX_DECLINED; + } +} + + +static ngx_int_t +ngx_stream_geoip_int(MMDB_entry_s *entry, const char **path, int64_t *value) +{ + MMDB_entry_data_s entry_data; + + if (path == NULL + || MMDB_aget_value(entry, &entry_data, path) != MMDB_SUCCESS + || !entry_data.has_data) + { + return NGX_DECLINED; + } + + switch (entry_data.type) { + + case MMDB_DATA_TYPE_UINT16: + *value = entry_data.uint16; + return NGX_OK; + + case MMDB_DATA_TYPE_UINT32: + *value = entry_data.uint32; + return NGX_OK; + + case MMDB_DATA_TYPE_INT32: + *value = entry_data.int32; + return NGX_OK; + + default: + return NGX_DECLINED; + } +} + + +static ngx_int_t +ngx_stream_geoip_lookup(ngx_stream_session_t *s, MMDB_s *mmdb, + MMDB_entry_s *entry) +{ + int err; + struct sockaddr *sa; + MMDB_lookup_result_s result; +#if (NGX_HAVE_INET6) + struct in6_addr *inaddr6; + struct sockaddr_in sin; +#endif + + sa = s->connection->sockaddr; + + switch (sa->sa_family) { + + case AF_INET: + break; + +#if (NGX_HAVE_INET6) + case AF_INET6: + inaddr6 = &((struct sockaddr_in6 *) sa)->sin6_addr; + + if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { + ngx_memzero(&sin, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + ngx_memcpy(&sin.sin_addr, &inaddr6->s6_addr[12], 4); + + sa = (struct sockaddr *) &sin; + break; + } + + if (mmdb->metadata.ip_version == 4) { + return NGX_DECLINED; + } + + break; +#endif + + default: + return NGX_DECLINED; + } + + result = MMDB_lookup_sockaddr(mmdb, sa, &err); + + if (err != MMDB_SUCCESS) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "MMDB_lookup_sockaddr() failed: %s", MMDB_strerror(err)); + return NGX_DECLINED; + } + + if (!result.found_entry) { + return NGX_DECLINED; + } + + *entry = result.entry; + + return NGX_OK; +} + +#endif + static ngx_int_t ngx_stream_geoip_add_variables(ngx_conf_t *cf) @@ -629,12 +1311,37 @@ ngx_stream_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; +#if (NGX_HAVE_GEOIP) if (gcf->country) { return "is duplicate"; } +#endif +#if (NGX_HAVE_MAXMINDDB) + if (gcf->country_mmdb) { + return "is duplicate"; + } +#endif value = cf->args->elts; +#if (NGX_HAVE_MAXMINDDB) + + switch (ngx_stream_geoip_open(cf, &value[1], &gcf->country_mmdb)) { + + case NGX_OK: + return ngx_stream_geoip_charset(cf); + + case NGX_ERROR: + return NGX_CONF_ERROR; + + default: + break; + } + +#endif + +#if (NGX_HAVE_GEOIP) + gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->country == NULL) { @@ -674,6 +1381,15 @@ ngx_stream_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) &value[1], gcf->country->databaseType); return NGX_CONF_ERROR; } + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" is not a MaxMind DB database", &value[1]); + + return NGX_CONF_ERROR; + +#endif } @@ -684,12 +1400,37 @@ ngx_stream_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; +#if (NGX_HAVE_GEOIP) if (gcf->org) { return "is duplicate"; } +#endif +#if (NGX_HAVE_MAXMINDDB) + if (gcf->org_mmdb) { + return "is duplicate"; + } +#endif value = cf->args->elts; +#if (NGX_HAVE_MAXMINDDB) + + switch (ngx_stream_geoip_open(cf, &value[1], &gcf->org_mmdb)) { + + case NGX_OK: + return ngx_stream_geoip_charset(cf); + + case NGX_ERROR: + return NGX_CONF_ERROR; + + default: + break; + } + +#endif + +#if (NGX_HAVE_GEOIP) + gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->org == NULL) { @@ -735,6 +1476,15 @@ ngx_stream_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) &value[1], gcf->org->databaseType); return NGX_CONF_ERROR; } + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" is not a MaxMind DB database", &value[1]); + + return NGX_CONF_ERROR; + +#endif } @@ -745,12 +1495,37 @@ ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; +#if (NGX_HAVE_GEOIP) if (gcf->city) { return "is duplicate"; } +#endif +#if (NGX_HAVE_MAXMINDDB) + if (gcf->city_mmdb) { + return "is duplicate"; + } +#endif value = cf->args->elts; +#if (NGX_HAVE_MAXMINDDB) + + switch (ngx_stream_geoip_open(cf, &value[1], &gcf->city_mmdb)) { + + case NGX_OK: + return ngx_stream_geoip_charset(cf); + + case NGX_ERROR: + return NGX_CONF_ERROR; + + default: + break; + } + +#endif + +#if (NGX_HAVE_GEOIP) + gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->city == NULL) { @@ -792,14 +1567,86 @@ ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) &value[1], gcf->city->databaseType); return NGX_CONF_ERROR; } + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" is not a MaxMind DB database", &value[1]); + + return NGX_CONF_ERROR; + +#endif +} + + +#if (NGX_HAVE_MAXMINDDB) + +static ngx_int_t +ngx_stream_geoip_open(ngx_conf_t *cf, ngx_str_t *name, MMDB_s **db) +{ + int status; + MMDB_s *mmdb; + ngx_file_info_t fi; + + if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_file_info_n " \"%V\" failed", name); + return NGX_ERROR; + } + + mmdb = ngx_palloc(cf->pool, sizeof(MMDB_s)); + if (mmdb == NULL) { + return NGX_ERROR; + } + + status = MMDB_open((char *) name->data, MMDB_MODE_MMAP, mmdb); + + if (status == MMDB_SUCCESS) { + *db = mmdb; + return NGX_OK; + } + + /* presumably a legacy GeoIP database */ + + if (status == MMDB_INVALID_METADATA_ERROR) { + return NGX_DECLINED; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "MMDB_open(\"%V\") failed: %s", + name, MMDB_strerror(status)); + + return NGX_ERROR; +} + + +static char * +ngx_stream_geoip_charset(ngx_conf_t *cf) +{ + ngx_str_t *value; + + value = cf->args->elts; + + /* MaxMind DB is always in UTF-8 */ + + if (cf->args->nelts == 3 && ngx_strcmp(value[2].data, "utf8") != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; } +#endif + static void ngx_stream_geoip_cleanup(void *data) { ngx_stream_geoip_conf_t *gcf = data; +#if (NGX_HAVE_GEOIP) + if (gcf->country) { GeoIP_delete(gcf->country); } @@ -811,4 +1658,22 @@ ngx_stream_geoip_cleanup(void *data) if (gcf->city) { GeoIP_delete(gcf->city); } + +#endif + +#if (NGX_HAVE_MAXMINDDB) + + if (gcf->country_mmdb) { + MMDB_close(gcf->country_mmdb); + } + + if (gcf->org_mmdb) { + MMDB_close(gcf->org_mmdb); + } + + if (gcf->city_mmdb) { + MMDB_close(gcf->city_mmdb); + } + +#endif }