]> git.kaiwu.me - nginx.git/commitdiff
GeoIP: support for the MaxMind DB (MMDB) databases
authorSergey Kandaurov <s.kandaurov@f5.com>
Tue, 8 Sep 2026 19:56:49 +0000 (19:56 +0000)
committerSergey Kandaurov <pluknet@nginx.com>
Wed, 30 Sep 2026 16:17:54 +0000 (20:17 +0400)
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 <n.swami@f5.com>
auto/lib/geoip/conf
src/http/modules/ngx_http_geoip_module.c
src/stream/ngx_stream_geoip_module.c

index 47165b15bad7ab8dc1e0721123109506ebbd94e3..9ca29f4d4d8c709ba4d53679c0dfb71ae9c8d929 100644 (file)
@@ -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 <GeoIP.h>"
     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 <maxminddb.h>"
+    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
index 5b8b11fc79cf68d5196322e47db5aa7b2fe0142e..05f3d8aa384438eda7f14a549f1eeffd87c9a25a 100644 (file)
@@ -9,19 +9,44 @@
 #include <ngx_core.h>
 #include <ngx_http.h>
 
+#if (NGX_HAVE_GEOIP)
 #include <GeoIP.h>
 #include <GeoIPCity.h>
+#endif
+#if (NGX_HAVE_MAXMINDDB)
+#include <maxminddb.h>
+#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
 }
index 3ee8f0e33e69c3a4b1f10b6c1c201c0ea869f60b..67896837d503d2564f205403ec7c5263852686b4 100644 (file)
@@ -9,19 +9,44 @@
 #include <ngx_core.h>
 #include <ngx_stream.h>
 
+#if (NGX_HAVE_GEOIP)
 #include <GeoIP.h>
 #include <GeoIPCity.h>
+#endif
+#if (NGX_HAVE_MAXMINDDB)
+#include <maxminddb.h>
+#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
 }