[PATCH 6 of 6] GeoIP: support for GeoIP2 databases in the MMDB format

Maxim Dounin mdounin at mdounin.ru
Wed Dec 3 16:31:10 UTC 2025


# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1764579291 -10800
#      Mon Dec 01 11:54:51 2025 +0300
# Node ID 234a79a752d46a21ecaccddfda660ca2cbfe15f4
# Parent  9d4db0c2b512cb8d548345db2852c2d6a000d49c
GeoIP: support for GeoIP2 databases in the MMDB format.

With this change, the ngx_http_geoip_module can be compiled with the
GeoIP library (legacy), or the libmaxminddb library, or both.  Usage
of specific libraries can be disabled during compilation with new configure
options, "--without-geoip" and "--without-libmaxminddb".

Existing directives (geoip_country, geoip_org, geoip_city) support both
file formats, and existing variables are mapped to the appropriate data
paths in the MaxMind GeoIP2 databases, for example:

    geoip_country /path/to/GeoLite2-Country.mmdb;

Notable exceptions: the $geoip_country_code3, $geoip_city_country_code3,
and $geoip_area_code variables are no longer available if GeoIP2 databases
are used, because the underlying fields are not available in the new
databases.

Additionally, the geoip_set directive is introduced.  It can be used to
access arbitrary data paths within an MMDB database, for example:

    geoip_set $country /path/to/GeoLite2-Country.mmdb country.names.en;
    geoip_set $asn /path/to/GeoLite2-ASN.mmdb autonomous_system_number;

The last parameter is the data path to look up in the database, similarly
to the one accepted by the mmdblookup utility (but with dots between
path components).

diff --git a/auto/lib/geoip/conf b/auto/lib/geoip/conf
--- a/auto/lib/geoip/conf
+++ b/auto/lib/geoip/conf
@@ -1,10 +1,15 @@
 
 # Copyright (C) Igor Sysoev
+# Copyright (C) Maxim Dounin
 # Copyright (C) Nginx, Inc.
 
 
+ngx_geoip_found=no
+
+if [ $GEOIP_LEGACY != DISABLED ]; then
+
     ngx_feature="GeoIP library"
-    ngx_feature_name=
+    ngx_feature_name="NGX_HAVE_GEOIP_LEGACY"
     ngx_feature_run=no
     ngx_feature_incs="#include <GeoIP.h>"
     ngx_feature_path=
@@ -12,84 +17,160 @@
     ngx_feature_test="GeoIP_open(NULL, 0)"
     . auto/feature
 
+    if [ $ngx_found = no ]; then
 
-if [ $ngx_found = no ]; then
+        # FreeBSD port
+
+        ngx_feature="GeoIP library in /usr/local/"
+        ngx_feature_path="/usr/local/include"
 
-    # FreeBSD port
+        if [ $NGX_RPATH = YES ]; then
+            ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lGeoIP"
+        else
+            ngx_feature_libs="-L/usr/local/lib -lGeoIP"
+        fi
 
-    ngx_feature="GeoIP library in /usr/local/"
-    ngx_feature_path="/usr/local/include"
+        . auto/feature
+    fi
+
+    if [ $ngx_found = no ]; then
+
+        # NetBSD port
 
-    if [ $NGX_RPATH = YES ]; then
-        ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lGeoIP"
-    else
-        ngx_feature_libs="-L/usr/local/lib -lGeoIP"
+        ngx_feature="GeoIP 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 -lGeoIP"
+        else
+            ngx_feature_libs="-L/usr/pkg/lib -lGeoIP"
+        fi
+
+        . auto/feature
     fi
 
-    . auto/feature
-fi
-
+    if [ $ngx_found = no ]; then
 
-if [ $ngx_found = no ]; then
+        # MacPorts
 
-    # NetBSD port
+        ngx_feature="GeoIP library in /opt/local/"
+        ngx_feature_path="/opt/local/include"
 
-    ngx_feature="GeoIP library in /usr/pkg/"
-    ngx_feature_path="/usr/pkg/include"
+        if [ $NGX_RPATH = YES ]; then
+            ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lGeoIP"
+        else
+            ngx_feature_libs="-L/opt/local/lib -lGeoIP"
+        fi
 
-    if [ $NGX_RPATH = YES ]; then
-        ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lGeoIP"
-    else
-        ngx_feature_libs="-L/usr/pkg/lib -lGeoIP"
+        . auto/feature
     fi
 
-    . auto/feature
+    if [ $ngx_found = yes ]; then
+
+        CORE_INCS="$CORE_INCS $ngx_feature_path"
+
+        if [ $USE_GEOIP = YES ]; then
+            CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+        fi
+
+        NGX_LIB_GEOIP=$ngx_feature_libs
+
+        ngx_feature="GeoIP IPv6 support"
+        ngx_feature_name="NGX_HAVE_GEOIP_V6"
+        ngx_feature_run=no
+        ngx_feature_incs="#include <stdio.h>
+                          #include <GeoIP.h>"
+        #ngx_feature_path=
+        #ngx_feature_libs=
+        ngx_feature_test="printf(\"%d\", GEOIP_CITY_EDITION_REV0_V6);"
+        . auto/feature
+
+        ngx_geoip_found=yes
+    fi
 fi
 
 
-if [ $ngx_found = no ]; then
+if [ $GEOIP_MMDB != DISABLED ]; then
 
-    # MacPorts
+    ngx_feature="libmaxminddb"
+    ngx_feature_name="NGX_HAVE_GEOIP_MMDB"
+    ngx_feature_run=no
+    ngx_feature_incs="#include <maxminddb.h>"
+    ngx_feature_path=
+    ngx_feature_libs="-lmaxminddb"
+    ngx_feature_test="MMDB_open(NULL, 0, NULL)"
+    . auto/feature
+
+    if [ $ngx_found = no ]; then
 
-    ngx_feature="GeoIP library in /opt/local/"
-    ngx_feature_path="/opt/local/include"
+        # FreeBSD port
+
+        ngx_feature="libmaxminddb in /usr/local/"
+        ngx_feature_path="/usr/local/include"
 
-    if [ $NGX_RPATH = YES ]; then
-        ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lGeoIP"
-    else
-        ngx_feature_libs="-L/opt/local/lib -lGeoIP"
+        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
 
-    . auto/feature
+    if [ $ngx_found = no ]; then
+
+        # NetBSD port
+
+        ngx_feature="libmaxminddb 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="libmaxminddb 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 = yes ]; then
+
+        ngx_geoip_found=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
 fi
 
 
-if [ $ngx_found = yes ]; then
-
-    CORE_INCS="$CORE_INCS $ngx_feature_path"
-
-    if [ $USE_GEOIP = YES ]; then
-        CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
-    fi
-
-    NGX_LIB_GEOIP=$ngx_feature_libs
-
-    ngx_feature="GeoIP IPv6 support"
-    ngx_feature_name="NGX_HAVE_GEOIP_V6"
-    ngx_feature_run=no
-    ngx_feature_incs="#include <stdio.h>
-                      #include <GeoIP.h>"
-    #ngx_feature_path=
-    #ngx_feature_libs=
-    ngx_feature_test="printf(\"%d\", GEOIP_CITY_EDITION_REV0_V6);"
-    . auto/feature
-
-else
+if [ $ngx_geoip_found != yes ]; then
 
 cat << END
 
-$0: error: the GeoIP module requires the GeoIP library.
-You can either do not enable the module or install the library.
+$0: error: the GeoIP modules require the GeoIP library or
+the libmaxminddb library (or both).  You can either do not enable
+the modules or install at least one of the libraries.
 
 END
 
diff --git a/auto/options b/auto/options
--- a/auto/options
+++ b/auto/options
@@ -165,7 +165,10 @@ NGX_PERL=perl
 
 USE_LIBXSLT=NO
 USE_LIBGD=NO
+
 USE_GEOIP=NO
+GEOIP_LEGACY=YES
+GEOIP_MMDB=YES
 
 NGX_GOOGLE_PERFTOOLS=NO
 NGX_CPP_TEST=NO
@@ -405,6 +408,9 @@ use the \"--with-mail_ssl_module\" optio
         --with-libatomic)                NGX_LIBATOMIC=YES          ;;
         --with-libatomic=*)              NGX_LIBATOMIC="$value"     ;;
 
+        --without-geoip)                 GEOIP_LEGACY=DISABLED      ;;
+        --without-libmaxminddb)          GEOIP_MMDB=DISABLED        ;;
+
         --test-build-devpoll)            NGX_TEST_BUILD_DEVPOLL=YES ;;
         --test-build-eventport)          NGX_TEST_BUILD_EVENTPORT=YES ;;
         --test-build-epoll)              NGX_TEST_BUILD_EPOLL=YES   ;;
@@ -601,6 +607,9 @@ cat << END
   --with-openssl=DIR                 set path to OpenSSL library sources
   --with-openssl-opt=OPTIONS         set additional build options for OpenSSL
 
+  --without-geoip                    do not use GeoIP library
+  --without-libmaxminddb             do not use libmaxminddb library
+
   --with-debug                       enable debug logging
 
 END
diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c
--- a/src/http/modules/ngx_http_geoip_module.c
+++ b/src/http/modules/ngx_http_geoip_module.c
@@ -1,6 +1,7 @@
 
 /*
  * Copyright (C) Igor Sysoev
+ * Copyright (C) Maxim Dounin
  * Copyright (C) Nginx, Inc.
  */
 
@@ -9,27 +10,59 @@
 #include <ngx_core.h>
 #include <ngx_http.h>
 
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
 #include <GeoIP.h>
 #include <GeoIPCity.h>
 
+#endif
 
-#define NGX_GEOIP_COUNTRY_CODE   0
-#define NGX_GEOIP_COUNTRY_CODE3  1
-#define NGX_GEOIP_COUNTRY_NAME   2
+#if (NGX_HAVE_GEOIP_MMDB)
+
+#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_CONTINENT_CODE  3
+#define NGX_GEOIP_REGION          4
+#define NGX_GEOIP_REGION_NAME     5
+#define NGX_GEOIP_CITY            6
+#define NGX_GEOIP_POSTAL_CODE     7
+#define NGX_GEOIP_LATITUDE        8
+#define NGX_GEOIP_LONGITUDE       9
+#define NGX_GEOIP_DMA_CODE        10
+#define NGX_GEOIP_AREA_CODE       11
 
 
 typedef struct {
-    GeoIP        *country;
-    GeoIP        *org;
-    GeoIP        *city;
+    void         *country;
+    void         *org;
+    void         *city;
     ngx_array_t  *proxies;    /* array of ngx_cidr_t */
+    ngx_array_t  *mmdb;       /* array of MMDB_s */
     ngx_flag_t    proxy_recursive;
-#if (NGX_HAVE_GEOIP_V6)
     unsigned      country_v6:1;
+    unsigned      country_mmdb:1;
     unsigned      org_v6:1;
+    unsigned      org_mmdb:1;
     unsigned      city_v6:1;
+    unsigned      city_mmdb:1;
+} ngx_http_geoip_conf_t;
+
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+typedef struct {
+    MMDB_s       *mmdb;
+    const char  **path;
+} ngx_http_geoip_variable_t;
+
 #endif
-} ngx_http_geoip_conf_t;
 
 
 static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r,
@@ -44,14 +77,25 @@ static ngx_int_t ngx_http_geoip_city_flo
     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_LEGACY)
 static GeoIPRecord *ngx_http_geoip_get_city_record(ngx_http_request_t *r);
+#endif
 
+#if (NGX_HAVE_GEOIP_MMDB)
+static ngx_int_t ngx_http_geoip_mmdb_city_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_mmdb_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 static u_long ngx_http_geoip_addr(ngx_http_request_t *r,
     ngx_http_geoip_conf_t *gcf);
 #if (NGX_HAVE_GEOIP_V6)
 static geoipv6_t ngx_http_geoip_addr_v6(ngx_http_request_t *r,
     ngx_http_geoip_conf_t *gcf);
 #endif
+#endif
 static struct sockaddr *ngx_http_geoip_sockaddr(ngx_http_request_t *r,
     ngx_http_geoip_conf_t *gcf);
 
@@ -64,6 +108,15 @@ static char *ngx_http_geoip_org(ngx_conf
     void *conf);
 static char *ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static char *ngx_http_geoip_set(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#if (NGX_HAVE_GEOIP_MMDB)
+static MMDB_s *ngx_http_geoip_mmdb_open(ngx_conf_t *cf,
+    ngx_http_geoip_conf_t *gcf, ngx_str_t *file);
+#endif
+#if (NGX_HAVE_GEOIP_LEGACY)
+static ngx_int_t ngx_http_geoip_mmdb_file(ngx_str_t *file);
+#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,
@@ -94,6 +147,13 @@ static ngx_command_t  ngx_http_geoip_com
       0,
       NULL },
 
+    { ngx_string("geoip_set"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
+      ngx_http_geoip_set,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
     { ngx_string("geoip_proxy"),
       NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
       ngx_http_geoip_proxy,
@@ -163,61 +223,103 @@ static ngx_http_variable_t  ngx_http_geo
 
     { ngx_string("geoip_city_continent_code"), NULL,
       ngx_http_geoip_city_variable,
-      offsetof(GeoIPRecord, continent_code), 0, 0 },
+      NGX_GEOIP_CONTINENT_CODE, 0, 0 },
 
     { ngx_string("geoip_city_country_code"), NULL,
       ngx_http_geoip_city_variable,
-      offsetof(GeoIPRecord, country_code), 0, 0 },
+      NGX_GEOIP_COUNTRY_CODE, 0, 0 },
 
     { ngx_string("geoip_city_country_code3"), NULL,
       ngx_http_geoip_city_variable,
-      offsetof(GeoIPRecord, country_code3), 0, 0 },
+      NGX_GEOIP_COUNTRY_CODE3, 0, 0 },
 
     { ngx_string("geoip_city_country_name"), NULL,
       ngx_http_geoip_city_variable,
-      offsetof(GeoIPRecord, country_name), 0, 0 },
+      NGX_GEOIP_COUNTRY_NAME, 0, 0 },
 
     { ngx_string("geoip_region"), NULL,
       ngx_http_geoip_city_variable,
-      offsetof(GeoIPRecord, region), 0, 0 },
+      NGX_GEOIP_REGION, 0, 0 },
 
     { ngx_string("geoip_region_name"), NULL,
       ngx_http_geoip_region_name_variable,
-      0, 0, 0 },
+      NGX_GEOIP_REGION_NAME, 0, 0 },
 
     { ngx_string("geoip_city"), NULL,
       ngx_http_geoip_city_variable,
-      offsetof(GeoIPRecord, city), 0, 0 },
+      NGX_GEOIP_CITY, 0, 0 },
 
     { ngx_string("geoip_postal_code"), NULL,
       ngx_http_geoip_city_variable,
-      offsetof(GeoIPRecord, postal_code), 0, 0 },
+      NGX_GEOIP_POSTAL_CODE, 0, 0 },
 
     { ngx_string("geoip_latitude"), NULL,
       ngx_http_geoip_city_float_variable,
-      offsetof(GeoIPRecord, latitude), 0, 0 },
+      NGX_GEOIP_LATITUDE, 0, 0 },
 
     { ngx_string("geoip_longitude"), NULL,
       ngx_http_geoip_city_float_variable,
-      offsetof(GeoIPRecord, longitude), 0, 0 },
+      NGX_GEOIP_LONGITUDE, 0, 0 },
 
     { ngx_string("geoip_dma_code"), NULL,
       ngx_http_geoip_city_int_variable,
-      offsetof(GeoIPRecord, dma_code), 0, 0 },
+      NGX_GEOIP_DMA_CODE, 0, 0 },
 
     { ngx_string("geoip_area_code"), NULL,
       ngx_http_geoip_city_int_variable,
-      offsetof(GeoIPRecord, area_code), 0, 0 },
+      NGX_GEOIP_AREA_CODE, 0, 0 },
 
       ngx_http_null_variable
 };
 
 
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+static uintptr_t  ngx_http_geoip_city_offsets[] = {
+    offsetof(GeoIPRecord, country_code),
+    offsetof(GeoIPRecord, country_code3),
+    offsetof(GeoIPRecord, country_name),
+    offsetof(GeoIPRecord, continent_code),
+    offsetof(GeoIPRecord, region),
+    0,                                                /* 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_GEOIP_MMDB)
+
+static const char  *ngx_http_geoip_mmdb_paths[][5] = {
+    { "country", "iso_code", NULL, NULL, NULL },
+    { NULL, NULL, NULL, NULL, NULL },                 /* country code3 */
+    { "country", "names", "en", NULL, NULL },
+    { "continent", "code", NULL, NULL, NULL },
+    { "subdivisions", "0", "iso_code", NULL, NULL },
+    { "subdivisions", "0", "names", "en", NULL },
+    { "city", "names", "en", NULL, NULL },
+    { "postal", "code", NULL, NULL, NULL },
+    { "location", "latitude", NULL, NULL, NULL },
+    { "location", "longitude", NULL, NULL, NULL },
+    { "location", "metro_code", NULL, NULL, NULL },
+    { NULL, NULL, NULL, NULL, NULL }                  /* area code */
+};
+
+#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_LEGACY)
     const char             *val;
+#endif
     ngx_http_geoip_conf_t  *gcf;
 
     gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
@@ -226,6 +328,21 @@ ngx_http_geoip_country_variable(ngx_http
         goto not_found;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    if (gcf->country_mmdb) {
+        ngx_http_geoip_variable_t  gv;
+
+        gv.mmdb = gcf->country;
+        gv.path = ngx_http_geoip_mmdb_paths[data];
+
+        return ngx_http_geoip_mmdb_variable(r, v, (uintptr_t) &gv);
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
 #if (NGX_HAVE_GEOIP_V6)
     if (gcf->country_v6) {
         if (data == NGX_GEOIP_COUNTRY_CODE) {
@@ -269,6 +386,8 @@ ngx_http_geoip_country_variable(ngx_http
 
     return NGX_OK;
 
+#endif
+
 not_found:
 
     v->not_found = 1;
@@ -281,8 +400,10 @@ 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_LEGACY)
     size_t                  len;
     char                   *val;
+#endif
     ngx_http_geoip_conf_t  *gcf;
 
     gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
@@ -291,6 +412,32 @@ ngx_http_geoip_org_variable(ngx_http_req
         goto not_found;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    if (gcf->org_mmdb) {
+        ngx_int_t                  rc;
+        ngx_http_geoip_variable_t  gv;
+
+        static const char *org[] = { "organization", NULL };
+        static const char *asorg[] = { "autonomous_system_organization", NULL };
+
+        gv.mmdb = gcf->org;
+        gv.path = org;
+
+        rc = ngx_http_geoip_mmdb_variable(r, v, (uintptr_t) &gv);
+
+        if (rc == NGX_OK && v->not_found) {
+            gv.path = asorg;
+            rc = ngx_http_geoip_mmdb_variable(r, v, (uintptr_t) &gv);
+        }
+
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
 #if (NGX_HAVE_GEOIP_V6)
     if (gcf->org_v6) {
         val = GeoIP_name_by_ipnum_v6(gcf->org, ngx_http_geoip_addr_v6(r, gcf));
@@ -323,6 +470,8 @@ ngx_http_geoip_org_variable(ngx_http_req
 
     return NGX_OK;
 
+#endif
+
 not_found:
 
     v->not_found = 1;
@@ -335,16 +484,32 @@ static ngx_int_t
 ngx_http_geoip_city_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data)
 {
+#if (NGX_HAVE_GEOIP_LEGACY)
     char         *val;
     size_t        len;
     GeoIPRecord  *gr;
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_int_t  rc;
+
+    rc = ngx_http_geoip_mmdb_city_variable(r, v, data);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
     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;
     }
@@ -373,6 +538,8 @@ no_value:
 
 not_found:
 
+#endif
+
     v->not_found = 1;
 
     return NGX_OK;
@@ -383,9 +550,25 @@ static ngx_int_t
 ngx_http_geoip_region_name_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data)
 {
+#if (NGX_HAVE_GEOIP_LEGACY)
     size_t        len;
     const char   *val;
     GeoIPRecord  *gr;
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_int_t  rc;
+
+    rc = ngx_http_geoip_mmdb_city_variable(r, v, data);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
     gr = ngx_http_geoip_get_city_record(r);
     if (gr == NULL) {
@@ -417,6 +600,8 @@ ngx_http_geoip_region_name_variable(ngx_
 
 not_found:
 
+#endif
+
     v->not_found = 1;
 
     return NGX_OK;
@@ -427,13 +612,28 @@ static ngx_int_t
 ngx_http_geoip_city_float_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data)
 {
+#if (NGX_HAVE_GEOIP_LEGACY)
     float         val;
     GeoIPRecord  *gr;
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_int_t  rc;
+
+    rc = ngx_http_geoip_mmdb_city_variable(r, v, data);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
     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);
@@ -442,7 +642,7 @@ ngx_http_geoip_city_float_variable(ngx_h
         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;
@@ -452,6 +652,14 @@ ngx_http_geoip_city_float_variable(ngx_h
     GeoIPRecord_delete(gr);
 
     return NGX_OK;
+
+not_found:
+
+#endif
+
+    v->not_found = 1;
+
+    return NGX_OK;
 }
 
 
@@ -459,13 +667,28 @@ 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_LEGACY)
     int           val;
     GeoIPRecord  *gr;
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_int_t  rc;
+
+    rc = ngx_http_geoip_mmdb_city_variable(r, v, data);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
     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);
@@ -474,7 +697,7 @@ ngx_http_geoip_city_int_variable(ngx_htt
         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;
@@ -484,9 +707,19 @@ ngx_http_geoip_city_int_variable(ngx_htt
     GeoIPRecord_delete(gr);
 
     return NGX_OK;
+
+not_found:
+
+#endif
+
+    v->not_found = 1;
+
+    return NGX_OK;
 }
 
 
+#if (NGX_HAVE_GEOIP_LEGACY)
+
 static GeoIPRecord *
 ngx_http_geoip_get_city_record(ngx_http_request_t *r)
 {
@@ -507,6 +740,172 @@ ngx_http_geoip_get_city_record(ngx_http_
     return NULL;
 }
 
+#endif
+
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+static ngx_int_t
+ngx_http_geoip_mmdb_city_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_geoip_conf_t      *gcf;
+    ngx_http_geoip_variable_t   gv;
+
+    gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
+
+    if (!gcf->city_mmdb) {
+        return NGX_DECLINED;
+    }
+
+    gv.mmdb = gcf->city;
+    gv.path = ngx_http_geoip_mmdb_paths[data];
+
+    return ngx_http_geoip_mmdb_variable(r, v, (uintptr_t) &gv);
+}
+
+
+static ngx_int_t
+ngx_http_geoip_mmdb_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_geoip_variable_t *gv = (ngx_http_geoip_variable_t *) data;
+
+    int                     status;
+    struct sockaddr        *sockaddr;
+    MMDB_entry_data_s       entry;
+    MMDB_lookup_result_s    result;
+    ngx_http_geoip_conf_t  *gcf;
+
+    if (gv->path[0] == NULL) {
+        goto not_found;
+    }
+
+    gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
+
+    sockaddr = ngx_http_geoip_sockaddr(r, gcf);
+
+    if (sockaddr->sa_family != AF_INET
+#if (NGX_HAVE_INET6)
+        && sockaddr->sa_family != AF_INET6
+#endif
+        )
+    {
+        goto not_found;
+    }
+
+    result = MMDB_lookup_sockaddr(gv->mmdb, sockaddr, &status);
+
+    if (status != MMDB_SUCCESS) {
+        if (status == MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR) {
+            goto not_found;
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "MMDB_lookup_sockaddr() failed: %s",
+                      MMDB_strerror(status));
+        return NGX_ERROR;
+    }
+
+    if (!result.found_entry) {
+        goto not_found;
+    }
+
+    status = MMDB_aget_value(&result.entry, &entry, gv->path);
+
+    if (status != MMDB_SUCCESS) {
+        if (status == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) {
+            goto not_found;
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "MMDB_aget_value() failed: %s",
+                      MMDB_strerror(status));
+        return NGX_ERROR;
+    }
+
+    if (!entry.has_data) {
+        goto not_found;
+    }
+
+    if (entry.type == MMDB_DATA_TYPE_UTF8_STRING) {
+        v->len = entry.data_size;
+        v->data = (u_char *) entry.utf8_string;
+
+    } else if (entry.type == MMDB_DATA_TYPE_BYTES) {
+        v->len = entry.data_size;
+        v->data = (u_char *) entry.bytes;
+
+    } else if (entry.type == MMDB_DATA_TYPE_DOUBLE
+               || entry.type == MMDB_DATA_TYPE_FLOAT)
+    {
+        v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN + 5);
+        if (v->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (entry.type == MMDB_DATA_TYPE_DOUBLE) {
+            v->len = ngx_sprintf(v->data, "%.4f", entry.double_value)
+                     - v->data;
+
+        } else { /* MMDB_DATA_TYPE_FLOAT */
+            v->len = ngx_sprintf(v->data, "%.4f", (double) entry.float_value)
+                     - v->data;
+        }
+
+    } else if (entry.type == MMDB_DATA_TYPE_INT32
+               || entry.type == MMDB_DATA_TYPE_UINT16
+               || entry.type == MMDB_DATA_TYPE_UINT32
+               || entry.type == MMDB_DATA_TYPE_UINT64
+               || entry.type == MMDB_DATA_TYPE_BOOLEAN)
+    {
+        v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN);
+        if (v->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (entry.type == MMDB_DATA_TYPE_INT32) {
+            v->len = ngx_sprintf(v->data, "%D", entry.int32) - v->data;
+
+        } else if (entry.type == MMDB_DATA_TYPE_UINT16) {
+            v->len = ngx_sprintf(v->data, "%uD", (uint32_t) entry.uint16)
+                     - v->data;
+
+        } else if (entry.type == MMDB_DATA_TYPE_UINT32) {
+            v->len = ngx_sprintf(v->data, "%uD", entry.uint32) - v->data;
+
+        } else if (entry.type == MMDB_DATA_TYPE_UINT64) {
+            v->len = ngx_sprintf(v->data, "%uL", entry.uint64) - v->data;
+
+        } else { /* MMDB_DATA_TYPE_BOOLEAN */
+            v->len = ngx_sprintf(v->data, "%uD", (uint32_t) entry.boolean)
+                     - v->data;
+        }
+
+    } else {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "MMDB_aget_value(): unexpected entry type, %d",
+                       entry.type);
+        goto not_found;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
 static u_long
 ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf)
@@ -587,6 +986,7 @@ ngx_http_geoip_addr_v6(ngx_http_request_
 }
 
 #endif
+#endif
 
 
 static struct sockaddr *
@@ -682,6 +1082,46 @@ ngx_http_geoip_country(ngx_conf_t *cf, n
         return NGX_CONF_ERROR;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_http_geoip_mmdb_file(&value[1]) != NGX_OK) {
+        goto legacy;
+    }
+
+#endif
+
+    gcf->country = ngx_http_geoip_mmdb_open(cf, gcf, &value[1]);
+
+    if (gcf->country == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    gcf->country_mmdb = 1;
+
+    if (cf->args->nelts == 3) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_http_geoip_mmdb_file(&value[1]) == NGX_OK) {
+        return "does not support mmdb databases on this platform";
+    }
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+legacy:
+
+#endif
+
     gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
 
     if (gcf->country == NULL) {
@@ -702,7 +1142,7 @@ ngx_http_geoip_country(ngx_conf_t *cf, n
         }
     }
 
-    switch (gcf->country->databaseType) {
+    switch (((GeoIP *) gcf->country)->databaseType) {
 
     case GEOIP_COUNTRY_EDITION:
 
@@ -718,9 +1158,11 @@ ngx_http_geoip_country(ngx_conf_t *cf, n
     default:
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "invalid GeoIP database \"%V\" type:%d",
-                           &value[1], gcf->country->databaseType);
+                           &value[1], ((GeoIP *) gcf->country)->databaseType);
         return NGX_CONF_ERROR;
     }
+
+#endif
 }
 
 
@@ -741,6 +1183,46 @@ ngx_http_geoip_org(ngx_conf_t *cf, ngx_c
         return NGX_CONF_ERROR;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_http_geoip_mmdb_file(&value[1]) != NGX_OK) {
+        goto legacy;
+    }
+
+#endif
+
+    gcf->org = ngx_http_geoip_mmdb_open(cf, gcf, &value[1]);
+
+    if (gcf->org == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    gcf->org_mmdb = 1;
+
+    if (cf->args->nelts == 3) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_http_geoip_mmdb_file(&value[1]) == NGX_OK) {
+        return "does not support mmdb databases on this platform";
+    }
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+legacy:
+
+#endif
+
     gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
 
     if (gcf->org == NULL) {
@@ -761,7 +1243,7 @@ ngx_http_geoip_org(ngx_conf_t *cf, ngx_c
         }
     }
 
-    switch (gcf->org->databaseType) {
+    switch (((GeoIP *) gcf->org)->databaseType) {
 
     case GEOIP_ISP_EDITION:
     case GEOIP_ORG_EDITION:
@@ -783,9 +1265,11 @@ ngx_http_geoip_org(ngx_conf_t *cf, ngx_c
     default:
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "invalid GeoIP database \"%V\" type:%d",
-                           &value[1], gcf->org->databaseType);
+                           &value[1], ((GeoIP *) gcf->org)->databaseType);
         return NGX_CONF_ERROR;
     }
+
+#endif
 }
 
 
@@ -806,6 +1290,46 @@ ngx_http_geoip_city(ngx_conf_t *cf, ngx_
         return NGX_CONF_ERROR;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_http_geoip_mmdb_file(&value[1]) != NGX_OK) {
+        goto legacy;
+    }
+
+#endif
+
+    gcf->city = ngx_http_geoip_mmdb_open(cf, gcf, &value[1]);
+
+    if (gcf->city == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    gcf->city_mmdb = 1;
+
+    if (cf->args->nelts == 3) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_http_geoip_mmdb_file(&value[1]) == NGX_OK) {
+        return "does not support mmdb databases on this platform";
+    }
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+legacy:
+
+#endif
+
     gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
 
     if (gcf->city == NULL) {
@@ -826,7 +1350,7 @@ ngx_http_geoip_city(ngx_conf_t *cf, ngx_
         }
     }
 
-    switch (gcf->city->databaseType) {
+    switch (((GeoIP *) gcf->city)->databaseType) {
 
     case GEOIP_CITY_EDITION_REV0:
     case GEOIP_CITY_EDITION_REV1:
@@ -844,12 +1368,168 @@ ngx_http_geoip_city(ngx_conf_t *cf, ngx_
     default:
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "invalid GeoIP City database \"%V\" type:%d",
-                           &value[1], gcf->city->databaseType);
+                           &value[1], ((GeoIP *) gcf->city)->databaseType);
+        return NGX_CONF_ERROR;
+    }
+
+#endif
+}
+
+
+static char *
+ngx_http_geoip_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_http_geoip_conf_t  *gcf = conf;
+
+    u_char                     *key;
+    ngx_str_t                  *value;
+    ngx_uint_t                  i, n;
+    ngx_http_variable_t        *v;
+    ngx_http_geoip_variable_t  *gv;
+
+    value = cf->args->elts;
+
+    if (value[1].data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    value[1].len--;
+    value[1].data++;
+
+    v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
+    if (v == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    gv = ngx_palloc(cf->pool, sizeof(ngx_http_geoip_variable_t));
+    if (gv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    v->get_handler = ngx_http_geoip_mmdb_variable;
+    v->data = (uintptr_t) gv;
+
+    /* database file name */
+
+    if (ngx_conf_full_name(cf->cycle, &value[2], 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    gv->mmdb = ngx_http_geoip_mmdb_open(cf, gcf, &value[2]);
+
+    if (gv->mmdb == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    /* data path specification */
+
+    n = 0;
+    for (i = 0; i < value[3].len; i++) {
+        if (value[3].data[i] == '.') {
+            n++;
+        }
+    }
+
+    gv->path = ngx_pcalloc(cf->pool, (n + 2) * sizeof(char *));
+    if (gv->path == NULL) {
         return NGX_CONF_ERROR;
     }
+
+    n = 0;
+    key = &value[3].data[0];
+
+    for (i = 0; i < value[3].len; i++) {
+        if (value[3].data[i] != '.') {
+            continue;
+        }
+
+        value[3].data[i] = '\0';
+        gv->path[n++] = (char *) key;
+        key = &value[3].data[i + 1];
+    }
+
+    gv->path[n++] = (char *) key;
+    gv->path[n] = NULL;
+
+    return NGX_CONF_OK;
+
+#else
+    return "is not supported on this platform";
+#endif
 }
 
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+static MMDB_s *
+ngx_http_geoip_mmdb_open(ngx_conf_t *cf, ngx_http_geoip_conf_t *gcf,
+    ngx_str_t *file)
+{
+    int          status;
+    MMDB_s      *mmdb;
+    ngx_uint_t   i;
+
+    if (gcf->mmdb == NULL) {
+        gcf->mmdb = ngx_array_create(cf->pool, 1, sizeof(MMDB_s));
+        if (gcf->mmdb == NULL) {
+            return NULL;
+        }
+    }
+
+    mmdb = gcf->mmdb->elts;
+    for (i = 0; i < gcf->mmdb->nelts; i++) {
+        if (ngx_strcmp(mmdb[i].filename, file->data) == 0) {
+            return &mmdb[i];
+        }
+    }
+
+    mmdb = ngx_array_push(gcf->mmdb);
+    if (mmdb == NULL) {
+        return NULL;
+    }
+
+    status = MMDB_open((char *) file->data, 0, mmdb);
+
+    if (status != MMDB_SUCCESS) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf,
+                           (status == MMDB_IO_ERROR) ? ngx_errno : 0,
+                           "MMDB_open(\"%V\") failed: %s",
+                           file, MMDB_strerror(status));
+
+        gcf->mmdb->nelts--;
+
+        return NULL;
+    }
+
+    return mmdb;
+}
+
+#endif
+
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+static ngx_int_t
+ngx_http_geoip_mmdb_file(ngx_str_t *file)
+{
+    if (file->len < sizeof(".mmdb") - 1
+        || ngx_strncasecmp(file->data + file->len - (sizeof(".mmdb") - 1),
+                           (u_char *) ".mmdb", sizeof(".mmdb") - 1)
+           != 0)
+    {
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
 static char *
 ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
@@ -916,15 +1596,33 @@ ngx_http_geoip_cleanup(void *data)
 {
     ngx_http_geoip_conf_t  *gcf = data;
 
-    if (gcf->country) {
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (gcf->country && !gcf->country_mmdb) {
         GeoIP_delete(gcf->country);
     }
 
-    if (gcf->org) {
+    if (gcf->org && !gcf->org_mmdb) {
         GeoIP_delete(gcf->org);
     }
 
-    if (gcf->city) {
+    if (gcf->city && !gcf->city_mmdb) {
         GeoIP_delete(gcf->city);
     }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    if (gcf->mmdb) {
+        MMDB_s      *mmdb;
+        ngx_uint_t   i;
+
+        mmdb = gcf->mmdb->elts;
+        for (i = 0; i < gcf->mmdb->nelts; i++) {
+            MMDB_close(&mmdb[i]);
+        }
+    }
+
+#endif
 }
diff --git a/src/stream/ngx_stream_geoip_module.c b/src/stream/ngx_stream_geoip_module.c
--- a/src/stream/ngx_stream_geoip_module.c
+++ b/src/stream/ngx_stream_geoip_module.c
@@ -1,6 +1,7 @@
 
 /*
  * Copyright (C) Igor Sysoev
+ * Copyright (C) Maxim Dounin
  * Copyright (C) Nginx, Inc.
  */
 
@@ -9,25 +10,56 @@
 #include <ngx_core.h>
 #include <ngx_stream.h>
 
+#if (NGX_HAVE_GEOIP_LEGACY)
+
 #include <GeoIP.h>
 #include <GeoIPCity.h>
 
+#endif
 
-#define NGX_GEOIP_COUNTRY_CODE   0
-#define NGX_GEOIP_COUNTRY_CODE3  1
-#define NGX_GEOIP_COUNTRY_NAME   2
+#if (NGX_HAVE_GEOIP_MMDB)
+
+#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_CONTINENT_CODE  3
+#define NGX_GEOIP_REGION          4
+#define NGX_GEOIP_REGION_NAME     5
+#define NGX_GEOIP_CITY            6
+#define NGX_GEOIP_POSTAL_CODE     7
+#define NGX_GEOIP_LATITUDE        8
+#define NGX_GEOIP_LONGITUDE       9
+#define NGX_GEOIP_DMA_CODE        10
+#define NGX_GEOIP_AREA_CODE       11
 
 
 typedef struct {
-    GeoIP        *country;
-    GeoIP        *org;
-    GeoIP        *city;
-#if (NGX_HAVE_GEOIP_V6)
+    void         *country;
+    void         *org;
+    void         *city;
+    ngx_array_t  *mmdb;       /* array of MMDB_s */
     unsigned      country_v6:1;
+    unsigned      country_mmdb:1;
     unsigned      org_v6:1;
+    unsigned      org_mmdb:1;
     unsigned      city_v6:1;
+    unsigned      city_mmdb:1;
+} ngx_stream_geoip_conf_t;
+
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+typedef struct {
+    MMDB_s       *mmdb;
+    const char  **path;
+} ngx_stream_geoip_variable_t;
+
 #endif
-} ngx_stream_geoip_conf_t;
 
 
 static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s,
@@ -42,14 +74,25 @@ static ngx_int_t ngx_stream_geoip_city_f
     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_LEGACY)
 static GeoIPRecord *ngx_stream_geoip_get_city_record(ngx_stream_session_t *s);
+#endif
 
+#if (NGX_HAVE_GEOIP_MMDB)
+static ngx_int_t ngx_stream_geoip_mmdb_city_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_geoip_mmdb_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 static u_long ngx_stream_geoip_addr(ngx_stream_session_t *s,
     ngx_stream_geoip_conf_t *gcf);
 #if (NGX_HAVE_GEOIP_V6)
 static geoipv6_t 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_add_variables(ngx_conf_t *cf);
 static void *ngx_stream_geoip_create_conf(ngx_conf_t *cf);
@@ -59,6 +102,15 @@ static char *ngx_stream_geoip_org(ngx_co
     void *conf);
 static char *ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static char *ngx_stream_geoip_set(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#if (NGX_HAVE_GEOIP_MMDB)
+static MMDB_s *ngx_stream_geoip_mmdb_open(ngx_conf_t *cf,
+    ngx_stream_geoip_conf_t *gcf, ngx_str_t *file);
+#endif
+#if (NGX_HAVE_GEOIP_LEGACY)
+static ngx_int_t ngx_stream_geoip_mmdb_file(ngx_str_t *file);
+#endif
 static void ngx_stream_geoip_cleanup(void *data);
 
 
@@ -85,6 +137,13 @@ static ngx_command_t  ngx_stream_geoip_c
       0,
       NULL },
 
+    { ngx_string("geoip_set"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE3,
+      ngx_stream_geoip_set,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
       ngx_null_command
 };
 
@@ -137,61 +196,103 @@ static ngx_stream_variable_t  ngx_stream
 
     { ngx_string("geoip_city_continent_code"), NULL,
       ngx_stream_geoip_city_variable,
-      offsetof(GeoIPRecord, continent_code), 0, 0 },
+      NGX_GEOIP_CONTINENT_CODE, 0, 0 },
 
     { ngx_string("geoip_city_country_code"), NULL,
       ngx_stream_geoip_city_variable,
-      offsetof(GeoIPRecord, country_code), 0, 0 },
+      NGX_GEOIP_COUNTRY_CODE, 0, 0 },
 
     { ngx_string("geoip_city_country_code3"), NULL,
       ngx_stream_geoip_city_variable,
-      offsetof(GeoIPRecord, country_code3), 0, 0 },
+      NGX_GEOIP_COUNTRY_CODE3, 0, 0 },
 
     { ngx_string("geoip_city_country_name"), NULL,
       ngx_stream_geoip_city_variable,
-      offsetof(GeoIPRecord, country_name), 0, 0 },
+      NGX_GEOIP_COUNTRY_NAME, 0, 0 },
 
     { ngx_string("geoip_region"), NULL,
       ngx_stream_geoip_city_variable,
-      offsetof(GeoIPRecord, region), 0, 0 },
+      NGX_GEOIP_REGION, 0, 0 },
 
     { ngx_string("geoip_region_name"), NULL,
       ngx_stream_geoip_region_name_variable,
-      0, 0, 0 },
+      NGX_GEOIP_REGION_NAME, 0, 0 },
 
     { ngx_string("geoip_city"), NULL,
       ngx_stream_geoip_city_variable,
-      offsetof(GeoIPRecord, city), 0, 0 },
+      NGX_GEOIP_CITY, 0, 0 },
 
     { ngx_string("geoip_postal_code"), NULL,
       ngx_stream_geoip_city_variable,
-      offsetof(GeoIPRecord, postal_code), 0, 0 },
+      NGX_GEOIP_POSTAL_CODE, 0, 0 },
 
     { ngx_string("geoip_latitude"), NULL,
       ngx_stream_geoip_city_float_variable,
-      offsetof(GeoIPRecord, latitude), 0, 0 },
+      NGX_GEOIP_LATITUDE, 0, 0 },
 
     { ngx_string("geoip_longitude"), NULL,
       ngx_stream_geoip_city_float_variable,
-      offsetof(GeoIPRecord, longitude), 0, 0 },
+      NGX_GEOIP_LONGITUDE, 0, 0 },
 
     { ngx_string("geoip_dma_code"), NULL,
       ngx_stream_geoip_city_int_variable,
-      offsetof(GeoIPRecord, dma_code), 0, 0 },
+      NGX_GEOIP_DMA_CODE, 0, 0 },
 
     { ngx_string("geoip_area_code"), NULL,
       ngx_stream_geoip_city_int_variable,
-      offsetof(GeoIPRecord, area_code), 0, 0 },
+      NGX_GEOIP_AREA_CODE, 0, 0 },
 
       ngx_stream_null_variable
 };
 
 
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+static uintptr_t  ngx_stream_geoip_city_offsets[] = {
+    offsetof(GeoIPRecord, country_code),
+    offsetof(GeoIPRecord, country_code3),
+    offsetof(GeoIPRecord, country_name),
+    offsetof(GeoIPRecord, continent_code),
+    offsetof(GeoIPRecord, region),
+    0,                                                /* 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_GEOIP_MMDB)
+
+static const char  *ngx_stream_geoip_mmdb_paths[][5] = {
+    { "country", "iso_code", NULL, NULL, NULL },
+    { NULL, NULL, NULL, NULL, NULL },                 /* country code3 */
+    { "country", "names", "en", NULL, NULL },
+    { "continent", "code", NULL, NULL, NULL },
+    { "subdivisions", "0", "iso_code", NULL, NULL },
+    { "subdivisions", "0", "names", "en", NULL },
+    { "city", "names", "en", NULL, NULL },
+    { "postal", "code", NULL, NULL, NULL },
+    { "location", "latitude", NULL, NULL, NULL },
+    { "location", "longitude", NULL, NULL, NULL },
+    { "location", "metro_code", NULL, NULL, NULL },
+    { NULL, NULL, NULL, NULL, NULL }                  /* area code */
+};
+
+#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_LEGACY)
     const char               *val;
+#endif
     ngx_stream_geoip_conf_t  *gcf;
 
     gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module);
@@ -200,6 +301,21 @@ ngx_stream_geoip_country_variable(ngx_st
         goto not_found;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    if (gcf->country_mmdb) {
+        ngx_stream_geoip_variable_t  gv;
+
+        gv.mmdb = gcf->country;
+        gv.path = ngx_stream_geoip_mmdb_paths[data];
+
+        return ngx_stream_geoip_mmdb_variable(s, v, (uintptr_t) &gv);
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
 #if (NGX_HAVE_GEOIP_V6)
     if (gcf->country_v6) {
         if (data == NGX_GEOIP_COUNTRY_CODE) {
@@ -243,6 +359,8 @@ ngx_stream_geoip_country_variable(ngx_st
 
     return NGX_OK;
 
+#endif
+
 not_found:
 
     v->not_found = 1;
@@ -255,8 +373,10 @@ 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_LEGACY)
     size_t                    len;
     char                     *val;
+#endif
     ngx_stream_geoip_conf_t  *gcf;
 
     gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module);
@@ -265,6 +385,32 @@ ngx_stream_geoip_org_variable(ngx_stream
         goto not_found;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    if (gcf->org_mmdb) {
+        ngx_int_t                    rc;
+        ngx_stream_geoip_variable_t  gv;
+
+        static const char *org[] = { "organization", NULL };
+        static const char *asorg[] = { "autonomous_system_organization", NULL };
+
+        gv.mmdb = gcf->org;
+        gv.path = org;
+
+        rc = ngx_stream_geoip_mmdb_variable(s, v, (uintptr_t) &gv);
+
+        if (rc == NGX_OK && v->not_found) {
+            gv.path = asorg;
+            rc = ngx_stream_geoip_mmdb_variable(s, v, (uintptr_t) &gv);
+        }
+
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
 #if (NGX_HAVE_GEOIP_V6)
     if (gcf->org_v6) {
         val = GeoIP_name_by_ipnum_v6(gcf->org,
@@ -298,6 +444,8 @@ ngx_stream_geoip_org_variable(ngx_stream
 
     return NGX_OK;
 
+#endif
+
 not_found:
 
     v->not_found = 1;
@@ -310,16 +458,32 @@ static ngx_int_t
 ngx_stream_geoip_city_variable(ngx_stream_session_t *s,
     ngx_stream_variable_value_t *v, uintptr_t data)
 {
+#if (NGX_HAVE_GEOIP_LEGACY)
     char         *val;
     size_t        len;
     GeoIPRecord  *gr;
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_int_t  rc;
+
+    rc = ngx_stream_geoip_mmdb_city_variable(s, v, data);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
     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;
     }
@@ -348,6 +512,8 @@ no_value:
 
 not_found:
 
+#endif
+
     v->not_found = 1;
 
     return NGX_OK;
@@ -358,9 +524,25 @@ static ngx_int_t
 ngx_stream_geoip_region_name_variable(ngx_stream_session_t *s,
     ngx_stream_variable_value_t *v, uintptr_t data)
 {
+#if (NGX_HAVE_GEOIP_LEGACY)
     size_t        len;
     const char   *val;
     GeoIPRecord  *gr;
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_int_t  rc;
+
+    rc = ngx_stream_geoip_mmdb_city_variable(s, v, data);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
     gr = ngx_stream_geoip_get_city_record(s);
     if (gr == NULL) {
@@ -392,6 +574,8 @@ ngx_stream_geoip_region_name_variable(ng
 
 not_found:
 
+#endif
+
     v->not_found = 1;
 
     return NGX_OK;
@@ -402,13 +586,28 @@ static ngx_int_t
 ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s,
     ngx_stream_variable_value_t *v, uintptr_t data)
 {
+#if (NGX_HAVE_GEOIP_LEGACY)
     float         val;
     GeoIPRecord  *gr;
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_int_t  rc;
+
+    rc = ngx_stream_geoip_mmdb_city_variable(s, v, data);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
     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);
@@ -417,7 +616,7 @@ ngx_stream_geoip_city_float_variable(ngx
         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;
@@ -427,6 +626,14 @@ ngx_stream_geoip_city_float_variable(ngx
     GeoIPRecord_delete(gr);
 
     return NGX_OK;
+
+not_found:
+
+#endif
+
+    v->not_found = 1;
+
+    return NGX_OK;
 }
 
 
@@ -434,13 +641,28 @@ 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_LEGACY)
     int           val;
     GeoIPRecord  *gr;
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_int_t  rc;
+
+    rc = ngx_stream_geoip_mmdb_city_variable(s, v, data);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
     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);
@@ -449,7 +671,7 @@ ngx_stream_geoip_city_int_variable(ngx_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;
@@ -459,9 +681,19 @@ ngx_stream_geoip_city_int_variable(ngx_s
     GeoIPRecord_delete(gr);
 
     return NGX_OK;
+
+not_found:
+
+#endif
+
+    v->not_found = 1;
+
+    return NGX_OK;
 }
 
 
+#if (NGX_HAVE_GEOIP_LEGACY)
+
 static GeoIPRecord *
 ngx_stream_geoip_get_city_record(ngx_stream_session_t *s)
 {
@@ -482,6 +714,169 @@ ngx_stream_geoip_get_city_record(ngx_str
     return NULL;
 }
 
+#endif
+
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+static ngx_int_t
+ngx_stream_geoip_mmdb_city_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_geoip_conf_t      *gcf;
+    ngx_stream_geoip_variable_t   gv;
+
+    gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module);
+
+    if (!gcf->city_mmdb) {
+        return NGX_DECLINED;
+    }
+
+    gv.mmdb = gcf->city;
+    gv.path = ngx_stream_geoip_mmdb_paths[data];
+
+    return ngx_stream_geoip_mmdb_variable(s, v, (uintptr_t) &gv);
+}
+
+
+static ngx_int_t
+ngx_stream_geoip_mmdb_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_geoip_variable_t *gv = (ngx_stream_geoip_variable_t *) data;
+
+    int                    status;
+    struct sockaddr       *sockaddr;
+    MMDB_entry_data_s      entry;
+    MMDB_lookup_result_s   result;
+
+    if (gv->path[0] == NULL) {
+        goto not_found;
+    }
+
+    sockaddr = s->connection->sockaddr;
+
+    if (sockaddr->sa_family != AF_INET
+#if (NGX_HAVE_INET6)
+        && sockaddr->sa_family != AF_INET6
+#endif
+        )
+    {
+        goto not_found;
+    }
+
+    result = MMDB_lookup_sockaddr(gv->mmdb, sockaddr, &status);
+
+    if (status != MMDB_SUCCESS) {
+        if (status == MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR) {
+            goto not_found;
+        }
+
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "MMDB_lookup_sockaddr() failed: %s",
+                      MMDB_strerror(status));
+        return NGX_ERROR;
+    }
+
+    if (!result.found_entry) {
+        goto not_found;
+    }
+
+    status = MMDB_aget_value(&result.entry, &entry, gv->path);
+
+    if (status != MMDB_SUCCESS) {
+        if (status == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) {
+            goto not_found;
+        }
+
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "MMDB_aget_value() failed: %s",
+                      MMDB_strerror(status));
+        return NGX_ERROR;
+    }
+
+    if (!entry.has_data) {
+        goto not_found;
+    }
+
+    if (entry.type == MMDB_DATA_TYPE_UTF8_STRING) {
+        v->len = entry.data_size;
+        v->data = (u_char *) entry.utf8_string;
+
+    } else if (entry.type == MMDB_DATA_TYPE_BYTES) {
+        v->len = entry.data_size;
+        v->data = (u_char *) entry.bytes;
+
+    } else if (entry.type == MMDB_DATA_TYPE_DOUBLE
+               || entry.type == MMDB_DATA_TYPE_FLOAT)
+    {
+        v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN + 5);
+        if (v->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (entry.type == MMDB_DATA_TYPE_DOUBLE) {
+            v->len = ngx_sprintf(v->data, "%.4f", entry.double_value)
+                     - v->data;
+
+        } else { /* MMDB_DATA_TYPE_FLOAT */
+            v->len = ngx_sprintf(v->data, "%.4f", (double) entry.float_value)
+                     - v->data;
+        }
+
+    } else if (entry.type == MMDB_DATA_TYPE_INT32
+               || entry.type == MMDB_DATA_TYPE_UINT16
+               || entry.type == MMDB_DATA_TYPE_UINT32
+               || entry.type == MMDB_DATA_TYPE_UINT64
+               || entry.type == MMDB_DATA_TYPE_BOOLEAN)
+    {
+        v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN);
+        if (v->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (entry.type == MMDB_DATA_TYPE_INT32) {
+            v->len = ngx_sprintf(v->data, "%D", entry.int32) - v->data;
+
+        } else if (entry.type == MMDB_DATA_TYPE_UINT16) {
+            v->len = ngx_sprintf(v->data, "%uD", (uint32_t) entry.uint16)
+                     - v->data;
+
+        } else if (entry.type == MMDB_DATA_TYPE_UINT32) {
+            v->len = ngx_sprintf(v->data, "%uD", entry.uint32) - v->data;
+
+        } else if (entry.type == MMDB_DATA_TYPE_UINT64) {
+            v->len = ngx_sprintf(v->data, "%uL", entry.uint64) - v->data;
+
+        } else { /* MMDB_DATA_TYPE_BOOLEAN */
+            v->len = ngx_sprintf(v->data, "%uD", (uint32_t) entry.boolean)
+                     - v->data;
+        }
+
+    } else {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
+                       "MMDB_aget_value(): unexpected entry type, %d",
+                       entry.type);
+        goto not_found;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+#if (NGX_HAVE_GEOIP_LEGACY)
 
 static u_long
 ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf)
@@ -562,6 +957,7 @@ ngx_stream_geoip_addr_v6(ngx_stream_sess
 }
 
 #endif
+#endif
 
 
 static ngx_int_t
@@ -623,6 +1019,46 @@ ngx_stream_geoip_country(ngx_conf_t *cf,
         return NGX_CONF_ERROR;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_stream_geoip_mmdb_file(&value[1]) != NGX_OK) {
+        goto legacy;
+    }
+
+#endif
+
+    gcf->country = ngx_stream_geoip_mmdb_open(cf, gcf, &value[1]);
+
+    if (gcf->country == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    gcf->country_mmdb = 1;
+
+    if (cf->args->nelts == 3) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_stream_geoip_mmdb_file(&value[1]) == NGX_OK) {
+        return "does not support mmdb databases on this platform";
+    }
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+legacy:
+
+#endif
+
     gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
 
     if (gcf->country == NULL) {
@@ -643,7 +1079,7 @@ ngx_stream_geoip_country(ngx_conf_t *cf,
         }
     }
 
-    switch (gcf->country->databaseType) {
+    switch (((GeoIP *) gcf->country)->databaseType) {
 
     case GEOIP_COUNTRY_EDITION:
 
@@ -659,9 +1095,11 @@ ngx_stream_geoip_country(ngx_conf_t *cf,
     default:
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "invalid GeoIP database \"%V\" type:%d",
-                           &value[1], gcf->country->databaseType);
+                           &value[1], ((GeoIP *) gcf->country)->databaseType);
         return NGX_CONF_ERROR;
     }
+
+#endif
 }
 
 
@@ -682,6 +1120,46 @@ ngx_stream_geoip_org(ngx_conf_t *cf, ngx
         return NGX_CONF_ERROR;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_stream_geoip_mmdb_file(&value[1]) != NGX_OK) {
+        goto legacy;
+    }
+
+#endif
+
+    gcf->org = ngx_stream_geoip_mmdb_open(cf, gcf, &value[1]);
+
+    if (gcf->org == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    gcf->org_mmdb = 1;
+
+    if (cf->args->nelts == 3) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_stream_geoip_mmdb_file(&value[1]) == NGX_OK) {
+        return "does not support mmdb databases on this platform";
+    }
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+legacy:
+
+#endif
+
     gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
 
     if (gcf->org == NULL) {
@@ -702,7 +1180,7 @@ ngx_stream_geoip_org(ngx_conf_t *cf, ngx
         }
     }
 
-    switch (gcf->org->databaseType) {
+    switch (((GeoIP *) gcf->org)->databaseType) {
 
     case GEOIP_ISP_EDITION:
     case GEOIP_ORG_EDITION:
@@ -724,9 +1202,11 @@ ngx_stream_geoip_org(ngx_conf_t *cf, ngx
     default:
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "invalid GeoIP database \"%V\" type:%d",
-                           &value[1], gcf->org->databaseType);
+                           &value[1], ((GeoIP *) gcf->org)->databaseType);
         return NGX_CONF_ERROR;
     }
+
+#endif
 }
 
 
@@ -747,6 +1227,46 @@ ngx_stream_geoip_city(ngx_conf_t *cf, ng
         return NGX_CONF_ERROR;
     }
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_stream_geoip_mmdb_file(&value[1]) != NGX_OK) {
+        goto legacy;
+    }
+
+#endif
+
+    gcf->city = ngx_stream_geoip_mmdb_open(cf, gcf, &value[1]);
+
+    if (gcf->city == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    gcf->city_mmdb = 1;
+
+    if (cf->args->nelts == 3) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+
+#endif
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (ngx_stream_geoip_mmdb_file(&value[1]) == NGX_OK) {
+        return "does not support mmdb databases on this platform";
+    }
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+legacy:
+
+#endif
+
     gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
 
     if (gcf->city == NULL) {
@@ -767,7 +1287,7 @@ ngx_stream_geoip_city(ngx_conf_t *cf, ng
         }
     }
 
-    switch (gcf->city->databaseType) {
+    switch (((GeoIP *) gcf->city)->databaseType) {
 
     case GEOIP_CITY_EDITION_REV0:
     case GEOIP_CITY_EDITION_REV1:
@@ -785,26 +1305,200 @@ ngx_stream_geoip_city(ngx_conf_t *cf, ng
     default:
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "invalid GeoIP City database \"%V\" type:%d",
-                           &value[1], gcf->city->databaseType);
+                           &value[1], ((GeoIP *) gcf->city)->databaseType);
+        return NGX_CONF_ERROR;
+    }
+
+#endif
+}
+
+
+static char *
+ngx_stream_geoip_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    ngx_stream_geoip_conf_t  *gcf = conf;
+
+    u_char                       *key;
+    ngx_str_t                    *value;
+    ngx_uint_t                    i, n;
+    ngx_stream_variable_t        *v;
+    ngx_stream_geoip_variable_t  *gv;
+
+    value = cf->args->elts;
+
+    if (value[1].data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    value[1].len--;
+    value[1].data++;
+
+    v = ngx_stream_add_variable(cf, &value[1], NGX_STREAM_VAR_CHANGEABLE);
+    if (v == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    gv = ngx_palloc(cf->pool, sizeof(ngx_stream_geoip_variable_t));
+    if (gv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    v->get_handler = ngx_stream_geoip_mmdb_variable;
+    v->data = (uintptr_t) gv;
+
+    /* database file name */
+
+    if (ngx_conf_full_name(cf->cycle, &value[2], 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    gv->mmdb = ngx_stream_geoip_mmdb_open(cf, gcf, &value[2]);
+
+    if (gv->mmdb == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    /* data path specification */
+
+    n = 0;
+    for (i = 0; i < value[3].len; i++) {
+        if (value[3].data[i] == '.') {
+            n++;
+        }
+    }
+
+    gv->path = ngx_pcalloc(cf->pool, (n + 2) * sizeof(char *));
+    if (gv->path == NULL) {
         return NGX_CONF_ERROR;
     }
+
+    n = 0;
+    key = &value[3].data[0];
+
+    for (i = 0; i < value[3].len; i++) {
+        if (value[3].data[i] != '.') {
+            continue;
+        }
+
+        value[3].data[i] = '\0';
+        gv->path[n++] = (char *) key;
+        key = &value[3].data[i + 1];
+    }
+
+    gv->path[n++] = (char *) key;
+    gv->path[n] = NULL;
+
+    return NGX_CONF_OK;
+
+#else
+    return "is not supported on this platform";
+#endif
 }
 
 
+#if (NGX_HAVE_GEOIP_MMDB)
+
+static MMDB_s *
+ngx_stream_geoip_mmdb_open(ngx_conf_t *cf, ngx_stream_geoip_conf_t *gcf,
+    ngx_str_t *file)
+{
+    int          status;
+    MMDB_s      *mmdb;
+    ngx_uint_t   i;
+
+    if (gcf->mmdb == NULL) {
+        gcf->mmdb = ngx_array_create(cf->pool, 1, sizeof(MMDB_s));
+        if (gcf->mmdb == NULL) {
+            return NULL;
+        }
+    }
+
+    mmdb = gcf->mmdb->elts;
+    for (i = 0; i < gcf->mmdb->nelts; i++) {
+        if (ngx_strcmp(mmdb[i].filename, file->data) == 0) {
+            return &mmdb[i];
+        }
+    }
+
+    mmdb = ngx_array_push(gcf->mmdb);
+    if (mmdb == NULL) {
+        return NULL;
+    }
+
+    status = MMDB_open((char *) file->data, 0, mmdb);
+
+    if (status != MMDB_SUCCESS) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf,
+                           (status == MMDB_IO_ERROR) ? ngx_errno : 0,
+                           "MMDB_open(\"%V\") failed: %s",
+                           file, MMDB_strerror(status));
+
+        gcf->mmdb->nelts--;
+
+        return NULL;
+    }
+
+    return mmdb;
+}
+
+#endif
+
+
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+static ngx_int_t
+ngx_stream_geoip_mmdb_file(ngx_str_t *file)
+{
+    if (file->len < sizeof(".mmdb") - 1
+        || ngx_strncasecmp(file->data + file->len - (sizeof(".mmdb") - 1),
+                           (u_char *) ".mmdb", sizeof(".mmdb") - 1)
+           != 0)
+    {
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
 static void
 ngx_stream_geoip_cleanup(void *data)
 {
     ngx_stream_geoip_conf_t  *gcf = data;
 
-    if (gcf->country) {
+#if (NGX_HAVE_GEOIP_LEGACY)
+
+    if (gcf->country && !gcf->country_mmdb) {
         GeoIP_delete(gcf->country);
     }
 
-    if (gcf->org) {
+    if (gcf->org && !gcf->org_mmdb) {
         GeoIP_delete(gcf->org);
     }
 
-    if (gcf->city) {
+    if (gcf->city && !gcf->city_mmdb) {
         GeoIP_delete(gcf->city);
     }
+
+#endif
+
+#if (NGX_HAVE_GEOIP_MMDB)
+
+    if (gcf->mmdb) {
+        MMDB_s      *mmdb;
+        ngx_uint_t   i;
+
+        mmdb = gcf->mmdb->elts;
+        for (i = 0; i < gcf->mmdb->nelts; i++) {
+            MMDB_close(&mmdb[i]);
+        }
+    }
+
+#endif
 }



More information about the nginx-devel mailing list