Upload Modules

This commit is contained in:
mxd
2026-05-17 11:34:54 +08:00
commit 9f28fed00a
171 changed files with 53743 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
Copyright (c) 2014, Lee Valentine <lee@leev.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,152 @@
Description
===========
**ngx_http_geoip2_module** - creates variables with values from the maxmind geoip2 databases based on the client IP (default) or from a specific variable (supports both IPv4 and IPv6)
The module now supports nginx streams and can be used in the same way the http module can be used.
## Installing
First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as described in its [README.md
file](https://github.com/maxmind/libmaxminddb/blob/main/README.md#installing-from-a-tarball).
#### Download nginx source
```
wget http://nginx.org/download/nginx-VERSION.tar.gz
tar zxvf nginx-VERSION.tar.gz
cd nginx-VERSION
```
##### To build as a dynamic module (nginx 1.9.11+):
```
./configure --with-compat --add-dynamic-module=/path/to/ngx_http_geoip2_module
make modules
```
This will produce ```objs/ngx_http_geoip2_module.so```. It can be copied to your nginx module path manually if you wish.
Add the following line to your nginx.conf:
```
load_module modules/ngx_http_geoip2_module.so;
```
##### To build as a static module:
```
./configure --add-module=/path/to/ngx_http_geoip2_module
make
make install
```
##### If you need stream support, make sure to compile with stream:
```
./configure --add-dynamic-module=/path/to/ngx_http_geoip2_module --with-stream
OR
./configure --add-module=/path/to/ngx_http_geoip2_module --with-stream
```
## Download Maxmind GeoLite2 Database (optional)
The free GeoLite2 databases are available from [Maxminds website](http://dev.maxmind.com/geoip/geoip2/geolite2/) (requires signing up)
## Example Usage:
```
http {
...
geoip2 /etc/maxmind-country.mmdb {
auto_reload 5m;
$geoip2_metadata_country_build metadata build_epoch;
$geoip2_data_country_code default=US source=$variable_with_ip country iso_code;
$geoip2_data_country_name country names en;
}
geoip2 /etc/maxmind-city.mmdb {
$geoip2_data_city_name default=London city names en;
}
....
fastcgi_param COUNTRY_CODE $geoip2_data_country_code;
fastcgi_param COUNTRY_NAME $geoip2_data_country_name;
fastcgi_param CITY_NAME $geoip2_data_city_name;
....
}
stream {
...
geoip2 /etc/maxmind-country.mmdb {
$geoip2_data_country_code default=US source=$remote_addr country iso_code;
}
...
}
```
##### Metadata:
Retrieve metadata regarding the geoip database.
```
$variable_name metadata <field>
```
Available fields:
- build_epoch: the build timestamp of the maxmind database.
- last_check: the last time the database was checked for changes (when using auto_reload)
- last_change: the last time the database was reloaded (when using auto_reload)
##### Autoreload (default: disabled):
Enabling auto reload will have nginx check the modification time of the database at the specified
interval and reload it if it has changed.
```
auto_reload <interval>
```
##### GeoIP:
```
$variable_name [default=<value] [source=$variable_with_ip] path ...
```
If default is not specified, the variable will be empty if not found.
If source is not specified, $remote_addr will be used to perform the lookup.
To find the path of the data you want (eg: country names en), use the [mmdblookup tool](https://maxmind.github.io/libmaxminddb/mmdblookup.html):
```
$ mmdblookup --file /usr/share/GeoIP/GeoIP2-Country.mmdb --ip 8.8.8.8
{
"country":
{
"geoname_id":
6252001 <uint32>
"iso_code":
"US" <utf8_string>
"names":
{
"de":
"USA" <utf8_string>
"en":
"United States" <utf8_string>
}
}
}
$ mmdblookup --file /usr/share/GeoIP/GeoIP2-Country.mmdb --ip 8.8.8.8 country names en
"United States" <utf8_string>
```
This translates to:
```
$country_name "default=United States" source=$remote_addr country names en
```
##### Additional Commands:
These commands works the same as the original ngx_http_geoip_module documented here: http://nginx.org/en/docs/http/ngx_http_geoip_module.html#geoip_proxy.
However, if you provide the `source=$variable_with_ip` option on a variable, these settings will be ignored for that particular variable.
```
geoip2_proxy < cidr >
```
Defines trusted addresses. When a request comes from a trusted address, an address from the "X-Forwarded-For" request header field will be used instead.
```
geoip2_proxy_recursive < on | off >
```
If recursive search is disabled then instead of the original client address that matches one of the trusted addresses, the last address sent in "X-Forwarded-For" will be used. If recursive search is enabled then instead of the original client address that matches one of the trusted addresses, the last non-trusted address sent in "X-Forwarded-For" will be used.

View File

@@ -0,0 +1,43 @@
ngx_feature="MaxmindDB library"
ngx_feature_name=
ngx_feature_run=no
ngx_feature_incs="#include <maxminddb.h>"
ngx_feature_libs=-lmaxminddb
ngx_feature_test="MMDB_s mmdb"
. auto/feature
ngx_addon_name="ngx_geoip2_module"
if [ $ngx_found = yes ]; then
if test -n "$ngx_module_link"; then
if [ $HTTP != NO ]; then
ngx_module_type=HTTP
ngx_module_name="ngx_http_geoip2_module"
ngx_module_incs=
ngx_module_deps=
ngx_module_srcs="$ngx_addon_dir/ngx_http_geoip2_module.c"
ngx_module_libs="$ngx_feature_libs"
. auto/module
fi
nginx_version=`awk '/^#define nginx_version / {print $3}' src/core/nginx.h`
if [ $STREAM != NO -a $nginx_version -gt 1011001 ]; then
ngx_module_type=STREAM
ngx_module_name="ngx_stream_geoip2_module"
ngx_module_incs=
ngx_module_deps=
ngx_module_srcs="$ngx_addon_dir/ngx_stream_geoip2_module.c"
ngx_module_libs="$ngx_feature_libs"
. auto/module
fi
else
HTTP_MODULES="$HTTP_MODULES ngx_http_geoip2_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_geoip2_module.c"
CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
fi
else
cat << END
$0: error: the geoip2 module requires the maxminddb library.
END
exit 1
fi

View File

@@ -0,0 +1,803 @@
/*
* Copyright (C) Lee Valentine <lee@leev.net>
*
* Based on nginx's 'ngx_http_geoip_module.c' by Igor Sysoev
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <maxminddb.h>
typedef struct {
MMDB_s mmdb;
MMDB_lookup_result_s result;
time_t last_check;
time_t last_change;
time_t check_interval;
#if (NGX_HAVE_INET6)
uint8_t address[16];
#else
unsigned long address;
#endif
ngx_queue_t queue;
} ngx_http_geoip2_db_t;
typedef struct {
ngx_queue_t databases;
ngx_array_t *proxies;
ngx_flag_t proxy_recursive;
} ngx_http_geoip2_conf_t;
typedef struct {
ngx_http_geoip2_db_t *database;
const char **lookup;
ngx_str_t default_value;
ngx_http_complex_value_t source;
} ngx_http_geoip2_ctx_t;
typedef struct {
ngx_http_geoip2_db_t *database;
ngx_str_t metavalue;
} ngx_http_geoip2_metadata_t;
static ngx_int_t ngx_http_geoip2_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_geoip2_metadata(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static void *ngx_http_geoip2_create_conf(ngx_conf_t *cf);
static char *ngx_http_geoip2_init_conf(ngx_conf_t *cf, void *conf);
static char *ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_geoip2_parse_config(ngx_conf_t *cf, ngx_command_t *dummy,
void *conf);
static char *ngx_http_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy,
void *conf);
static char *ngx_http_geoip2_add_variable_geodata(ngx_conf_t *cf,
ngx_http_geoip2_db_t *database);
static char *ngx_http_geoip2_add_variable_metadata(ngx_conf_t *cf,
ngx_http_geoip2_db_t *database);
static char *ngx_http_geoip2_proxy(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_int_t ngx_http_geoip2_cidr_value(ngx_conf_t *cf, ngx_str_t *net,
ngx_cidr_t *cidr);
static void ngx_http_geoip2_cleanup(void *data);
static ngx_int_t ngx_http_geoip2_init(ngx_conf_t *cf);
#define FORMAT(fmt, ...) do { \
p = ngx_palloc(r->pool, NGX_OFF_T_LEN); \
if (p == NULL) { \
return NGX_ERROR; \
} \
v->len = ngx_sprintf(p, fmt, __VA_ARGS__) - p; \
v->data = p; \
} while (0)
static ngx_command_t ngx_http_geoip2_commands[] = {
{ ngx_string("geoip2"),
NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
ngx_http_geoip2,
NGX_HTTP_MAIN_CONF_OFFSET,
0,
NULL },
{ ngx_string("geoip2_proxy"),
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_http_geoip2_proxy,
NGX_HTTP_MAIN_CONF_OFFSET,
0,
NULL },
{ ngx_string("geoip2_proxy_recursive"),
NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_MAIN_CONF_OFFSET,
offsetof(ngx_http_geoip2_conf_t, proxy_recursive),
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_geoip2_module_ctx = {
NULL, /* preconfiguration */
ngx_http_geoip2_init, /* postconfiguration */
ngx_http_geoip2_create_conf, /* create main configuration */
ngx_http_geoip2_init_conf, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_geoip2_module = {
NGX_MODULE_V1,
&ngx_http_geoip2_module_ctx, /* module context */
ngx_http_geoip2_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_int_t
ngx_http_geoip2_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
uintptr_t data)
{
ngx_http_geoip2_ctx_t *geoip2 = (ngx_http_geoip2_ctx_t *) data;
ngx_http_geoip2_db_t *database = geoip2->database;
int mmdb_error;
MMDB_entry_data_s entry_data;
ngx_http_geoip2_conf_t *gcf;
ngx_addr_t addr;
#if defined(nginx_version) && nginx_version >= 1023000
ngx_table_elt_t *xfwd;
#else
ngx_array_t *xfwd;
#endif
u_char *p;
ngx_str_t val;
#if (NGX_HAVE_INET6)
uint8_t address[16], *addressp = address;
#else
unsigned long address;
#endif
if (geoip2->source.value.len > 0) {
if (ngx_http_complex_value(r, &geoip2->source, &val) != NGX_OK) {
goto not_found;
}
if (ngx_parse_addr(r->pool, &addr, val.data, val.len) != NGX_OK) {
goto not_found;
}
} else {
gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip2_module);
addr.sockaddr = r->connection->sockaddr;
addr.socklen = r->connection->socklen;
#if defined(nginx_version) && nginx_version >= 1023000
xfwd = r->headers_in.x_forwarded_for;
if (xfwd != NULL && gcf->proxies != NULL) {
#else
xfwd = &r->headers_in.x_forwarded_for;
if (xfwd->nelts > 0 && gcf->proxies != NULL) {
#endif
(void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL,
gcf->proxies, gcf->proxy_recursive);
}
}
switch (addr.sockaddr->sa_family) {
case AF_INET:
#if (NGX_HAVE_INET6)
ngx_memset(addressp, 0, 12);
ngx_memcpy(addressp + 12, &((struct sockaddr_in *)
addr.sockaddr)->sin_addr.s_addr, 4);
break;
case AF_INET6:
ngx_memcpy(addressp, &((struct sockaddr_in6 *)
addr.sockaddr)->sin6_addr.s6_addr, 16);
#else
address = ((struct sockaddr_in *)addr.sockaddr)->sin_addr.s_addr;
#endif
break;
default:
goto not_found;
}
#if (NGX_HAVE_INET6)
if (ngx_memcmp(&address, &database->address, sizeof(address))
!= 0) {
#else
if (address != database->address) {
#endif
memcpy(&database->address, &address, sizeof(address));
database->result = MMDB_lookup_sockaddr(&database->mmdb,
addr.sockaddr, &mmdb_error);
if (mmdb_error != MMDB_SUCCESS) {
goto not_found;
}
}
if (!database->result.found_entry
|| MMDB_aget_value(&database->result.entry, &entry_data,
geoip2->lookup) != MMDB_SUCCESS) {
goto not_found;
}
if (!entry_data.has_data) {
goto not_found;
}
switch (entry_data.type) {
case MMDB_DATA_TYPE_BOOLEAN:
FORMAT("%d", entry_data.boolean);
break;
case MMDB_DATA_TYPE_UTF8_STRING:
v->len = entry_data.data_size;
v->data = ngx_pnalloc(r->pool, v->len);
if (v->data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(v->data, (u_char *) entry_data.utf8_string, v->len);
break;
case MMDB_DATA_TYPE_BYTES:
v->len = entry_data.data_size;
v->data = ngx_pnalloc(r->pool, v->len);
if (v->data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(v->data, (u_char *) entry_data.bytes, v->len);
break;
case MMDB_DATA_TYPE_FLOAT:
FORMAT("%.5f", entry_data.float_value);
break;
case MMDB_DATA_TYPE_DOUBLE:
FORMAT("%.5f", entry_data.double_value);
break;
case MMDB_DATA_TYPE_UINT16:
FORMAT("%uD", entry_data.uint16);
break;
case MMDB_DATA_TYPE_UINT32:
FORMAT("%uD", entry_data.uint32);
break;
case MMDB_DATA_TYPE_INT32:
FORMAT("%D", entry_data.int32);
break;
case MMDB_DATA_TYPE_UINT64:
FORMAT("%uL", entry_data.uint64);
break;
case MMDB_DATA_TYPE_UINT128: ;
#if MMDB_UINT128_IS_BYTE_ARRAY
uint8_t *val = (uint8_t *)entry_data.uint128;
FORMAT( "0x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x",
val[0], val[1], val[2], val[3],
val[4], val[5], val[6], val[7],
val[8], val[9], val[10], val[11],
val[12], val[13], val[14], val[15]);
#else
mmdb_uint128_t val = entry_data.uint128;
FORMAT("0x%016uxL%016uxL",
(uint64_t) (val >> 64), (uint64_t) val);
#endif
break;
default:
goto not_found;
}
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
return NGX_OK;
not_found:
if (geoip2->default_value.len > 0) {
v->data = geoip2->default_value.data;
v->len = geoip2->default_value.len;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
} else {
v->not_found = 1;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_geoip2_metadata(ngx_http_request_t *r, ngx_http_variable_value_t *v,
uintptr_t data)
{
ngx_http_geoip2_metadata_t *metadata = (ngx_http_geoip2_metadata_t *) data;
ngx_http_geoip2_db_t *database = metadata->database;
u_char *p;
if (ngx_strncmp(metadata->metavalue.data, "build_epoch", 11) == 0) {
FORMAT("%uL", database->mmdb.metadata.build_epoch);
} else if (ngx_strncmp(metadata->metavalue.data, "last_check", 10) == 0) {
FORMAT("%T", database->last_check);
} else if (ngx_strncmp(metadata->metavalue.data, "last_change", 11) == 0) {
FORMAT("%T", database->last_change);
} else {
v->not_found = 1;
return NGX_OK;
}
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
return NGX_OK;
}
static void *
ngx_http_geoip2_create_conf(ngx_conf_t *cf)
{
ngx_pool_cleanup_t *cln;
ngx_http_geoip2_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip2_conf_t));
if (conf == NULL) {
return NULL;
}
conf->proxy_recursive = NGX_CONF_UNSET;
cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
return NULL;
}
ngx_queue_init(&conf->databases);
cln->handler = ngx_http_geoip2_cleanup;
cln->data = conf;
return conf;
}
static char *
ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_geoip2_conf_t *gcf = conf;
ngx_str_t *value;
int status;
ngx_http_geoip2_db_t *database;
char *rv;
ngx_conf_t save;
ngx_queue_t *q;
value = cf->args->elts;
if (value[1].data && value[1].data[0] != '/') {
if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
if (!ngx_queue_empty(&gcf->databases)) {
for (q = ngx_queue_head(&gcf->databases);
q != ngx_queue_sentinel(&gcf->databases);
q = ngx_queue_next(q))
{
database = ngx_queue_data(q, ngx_http_geoip2_db_t, queue);
if (ngx_strcmp(value[1].data, database->mmdb.filename) == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"Duplicate GeoIP2 mmdb - %V", &value[1]);
return NGX_CONF_ERROR;
}
}
}
database = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip2_db_t));
if (database == NULL) {
return NGX_CONF_ERROR;
}
ngx_queue_insert_tail(&gcf->databases, &database->queue);
database->last_check = database->last_change = ngx_time();
status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb);
if (status != MMDB_SUCCESS) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"MMDB_open(\"%V\") failed - %s", &value[1],
MMDB_strerror(status));
return NGX_CONF_ERROR;
}
save = *cf;
cf->handler = ngx_http_geoip2_parse_config;
cf->handler_conf = (void *) database;
rv = ngx_conf_parse(cf, NULL);
*cf = save;
return rv;
}
static char *
ngx_http_geoip2_parse_config(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{
ngx_http_geoip2_db_t *database;
ngx_str_t *value;
time_t interval;
value = cf->args->elts;
if (value[0].data[0] == '$') {
return ngx_http_geoip2_add_variable(cf, dummy, conf);
}
if (value[0].len == 11
&& ngx_strncmp(value[0].data, "auto_reload", 11) == 0) {
if ((int) cf->args->nelts != 2) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number of arguments for auto_reload");
return NGX_CONF_ERROR;
}
interval = ngx_parse_time(&value[1], true);
if (interval == (time_t) NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid interval for auto_reload \"%V\"",
&value[1]);
return NGX_CONF_ERROR;
}
database = (ngx_http_geoip2_db_t *) conf;
database->check_interval = interval;
return NGX_CONF_OK;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid setting \"%V\"", &value[0]);
return NGX_CONF_ERROR;
}
static char *
ngx_http_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{
ngx_http_geoip2_db_t *database;
ngx_str_t *value;
int nelts;
value = cf->args->elts;
if (value[0].data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[0]);
return NGX_CONF_ERROR;
}
value[0].len--;
value[0].data++;
nelts = (int) cf->args->nelts;
database = (ngx_http_geoip2_db_t *) conf;
if (nelts > 0 && value[1].len == 8 && ngx_strncmp(value[1].data, "metadata", 8) == 0) {
return ngx_http_geoip2_add_variable_metadata(cf, database);
}
return ngx_http_geoip2_add_variable_geodata(cf, database);
}
static char *
ngx_http_geoip2_add_variable_metadata(ngx_conf_t *cf, ngx_http_geoip2_db_t *database)
{
ngx_http_geoip2_metadata_t *metadata;
ngx_str_t *value, name;
ngx_http_variable_t *var;
metadata = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip2_metadata_t));
if (metadata == NULL) {
return NGX_CONF_ERROR;
}
value = cf->args->elts;
name = value[0];
metadata->database = database;
metadata->metavalue = value[2];
var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
if (var == NULL) {
return NGX_CONF_ERROR;
}
var->get_handler = ngx_http_geoip2_metadata;
var->data = (uintptr_t) metadata;
return NGX_CONF_OK;
}
static char *
ngx_http_geoip2_add_variable_geodata(ngx_conf_t *cf, ngx_http_geoip2_db_t *database)
{
ngx_http_geoip2_ctx_t *geoip2;
ngx_http_compile_complex_value_t ccv;
ngx_str_t *value, name, source;
ngx_http_variable_t *var;
int i, nelts, idx;
geoip2 = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip2_ctx_t));
if (geoip2 == NULL) {
return NGX_CONF_ERROR;
}
geoip2->database = database;
ngx_str_null(&source);
value = cf->args->elts;
name = value[0];
nelts = (int) cf->args->nelts;
idx = 1;
if (nelts > idx) {
for (i = idx; i < nelts; i++) {
if (ngx_strnstr(value[idx].data, "=", value[idx].len) == NULL) {
break;
}
if (value[idx].len > 8 && ngx_strncmp(value[idx].data, "default=", 8) == 0) {
if (geoip2->default_value.len > 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"default has already been declared for \"$%V\"", &name);
return NGX_CONF_ERROR;
}
geoip2->default_value.len = value[idx].len - 8;
geoip2->default_value.data = value[idx].data + 8;
} else if (value[idx].len > 7 && ngx_strncmp(value[idx].data, "source=", 7) == 0) {
if (source.len > 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"source has already been declared for \"$%V\"", &name);
return NGX_CONF_ERROR;
}
source.len = value[idx].len - 7;
source.data = value[idx].data + 7;
if (source.data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid source variable name \"%V\"", &source);
return NGX_CONF_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &source;
ccv.complex_value = &geoip2->source;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"unable to compile \"%V\" for \"$%V\"", &source, &name);
return NGX_CONF_ERROR;
}
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid setting \"%V\" for \"$%V\"", &value[idx], &name);
return NGX_CONF_ERROR;
}
idx++;
}
}
var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
if (var == NULL) {
return NGX_CONF_ERROR;
}
geoip2->lookup = ngx_pcalloc(cf->pool, sizeof(const char *) *
(cf->args->nelts - (idx - 1)));
if (geoip2->lookup == NULL) {
return NGX_CONF_ERROR;
}
for (i = idx; i < nelts; i++) {
geoip2->lookup[i - idx] = (char *) value[i].data;
}
geoip2->lookup[i - idx] = NULL;
var->get_handler = ngx_http_geoip2_variable;
var->data = (uintptr_t) geoip2;
return NGX_CONF_OK;
}
static char *
ngx_http_geoip2_init_conf(ngx_conf_t *cf, void *conf)
{
ngx_http_geoip2_conf_t *gcf = conf;
ngx_conf_init_value(gcf->proxy_recursive, 0);
return NGX_CONF_OK;
}
static char *
ngx_http_geoip2_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_geoip2_conf_t *gcf = conf;
ngx_str_t *value;
ngx_cidr_t cidr, *c;
value = cf->args->elts;
if (ngx_http_geoip2_cidr_value(cf, &value[1], &cidr) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (gcf->proxies == NULL) {
gcf->proxies = ngx_array_create(cf->pool, 4, sizeof(ngx_cidr_t));
if (gcf->proxies == NULL) {
return NGX_CONF_ERROR;
}
}
c = ngx_array_push(gcf->proxies);
if (c == NULL) {
return NGX_CONF_ERROR;
}
*c = cidr;
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_geoip2_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr)
{
ngx_int_t rc;
if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
cidr->family = AF_INET;
cidr->u.in.addr = 0xffffffff;
cidr->u.in.mask = 0xffffffff;
return NGX_OK;
}
rc = ngx_ptocidr(net, cidr);
if (rc == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid network \"%V\"", net);
return NGX_ERROR;
}
if (rc == NGX_DONE) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"low address bits of %V are meaningless", net);
}
return NGX_OK;
}
static void
ngx_http_geoip2_cleanup(void *data)
{
ngx_http_geoip2_conf_t *gcf = data;
ngx_queue_t *q;
ngx_http_geoip2_db_t *database;
while (!ngx_queue_empty(&gcf->databases)) {
q = ngx_queue_head(&gcf->databases);
ngx_queue_remove(q);
database = ngx_queue_data(q, ngx_http_geoip2_db_t, queue);
MMDB_close(&database->mmdb);
}
}
static ngx_int_t
ngx_http_geoip2_log_handler(ngx_http_request_t *r)
{
int status;
MMDB_s tmpdb;
ngx_queue_t *q;
ngx_file_info_t fi;
ngx_http_geoip2_db_t *database;
ngx_http_geoip2_conf_t *gcf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"geoip2 http log handler");
gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip2_module);
if (ngx_queue_empty(&gcf->databases)) {
return NGX_OK;
}
for (q = ngx_queue_head(&gcf->databases);
q != ngx_queue_sentinel(&gcf->databases);
q = ngx_queue_next(q))
{
database = ngx_queue_data(q, ngx_http_geoip2_db_t, queue);
if (database->check_interval == 0) {
continue;
}
if ((database->last_check + database->check_interval)
> ngx_time())
{
continue;
}
database->last_check = ngx_time();
if (ngx_file_info(database->mmdb.filename, &fi) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno,
ngx_file_info_n " \"%s\" failed",
database->mmdb.filename);
continue;
}
if (ngx_file_mtime(&fi) <= database->last_change) {
continue;
}
/* do the reload */
ngx_memzero(&tmpdb, sizeof(MMDB_s));
status = MMDB_open(database->mmdb.filename, MMDB_MODE_MMAP, &tmpdb);
if (status != MMDB_SUCCESS) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"MMDB_open(\"%s\") failed to reload - %s",
database->mmdb.filename, MMDB_strerror(status));
continue;
}
database->last_change = ngx_file_mtime(&fi);
MMDB_close(&database->mmdb);
database->mmdb = tmpdb;
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"Reload MMDB \"%s\"",
database->mmdb.filename);
}
return NGX_OK;
}
static ngx_int_t
ngx_http_geoip2_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_geoip2_log_handler;
return NGX_OK;
}

View File

@@ -0,0 +1,694 @@
/*
* Copyright (C) Lee Valentine <lee@leev.net>
* Copyright (C) Andrei Belov <defanator@gmail.com>
*
* Based on nginx's 'ngx_stream_geoip_module.c' by Igor Sysoev
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>
#include <maxminddb.h>
typedef struct {
MMDB_s mmdb;
MMDB_lookup_result_s result;
time_t last_check;
time_t last_change;
time_t check_interval;
#if (NGX_HAVE_INET6)
uint8_t address[16];
#else
unsigned long address;
#endif
ngx_queue_t queue;
} ngx_stream_geoip2_db_t;
typedef struct {
ngx_queue_t databases;
} ngx_stream_geoip2_conf_t;
typedef struct {
ngx_stream_geoip2_db_t *database;
const char **lookup;
ngx_str_t default_value;
ngx_stream_complex_value_t source;
} ngx_stream_geoip2_ctx_t;
typedef struct {
ngx_stream_geoip2_db_t *database;
ngx_str_t metavalue;
} ngx_stream_geoip2_metadata_t;
static ngx_int_t ngx_stream_geoip2_variable(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_stream_geoip2_metadata(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data);
static void *ngx_stream_geoip2_create_conf(ngx_conf_t *cf);
static char *ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_stream_geoip2_parse_config(ngx_conf_t *cf, ngx_command_t *dummy,
void *conf);
static char *ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy,
void *conf);
static char *ngx_stream_geoip2_add_variable_geodata(ngx_conf_t *cf,
ngx_stream_geoip2_db_t *database);
static char *ngx_stream_geoip2_add_variable_metadata(ngx_conf_t *cf,
ngx_stream_geoip2_db_t *database);
static void ngx_stream_geoip2_cleanup(void *data);
static ngx_int_t ngx_stream_geoip2_init(ngx_conf_t *cf);
#define FORMAT(fmt, ...) do { \
p = ngx_palloc(s->connection->pool, NGX_OFF_T_LEN); \
if (p == NULL) { \
return NGX_ERROR; \
} \
v->len = ngx_sprintf(p, fmt, __VA_ARGS__) - p; \
v->data = p; \
} while (0)
static ngx_command_t ngx_stream_geoip2_commands[] = {
{ ngx_string("geoip2"),
NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
ngx_stream_geoip2,
NGX_STREAM_MAIN_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_stream_module_t ngx_stream_geoip2_module_ctx = {
NULL, /* preconfiguration */
ngx_stream_geoip2_init, /* postconfiguration */
ngx_stream_geoip2_create_conf, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL /* merge server configuration */
};
ngx_module_t ngx_stream_geoip2_module = {
NGX_MODULE_V1,
&ngx_stream_geoip2_module_ctx, /* module context */
ngx_stream_geoip2_commands, /* module directives */
NGX_STREAM_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_int_t
ngx_stream_geoip2_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v,
uintptr_t data)
{
int mmdb_error;
u_char *p;
ngx_str_t val;
ngx_addr_t addr;
MMDB_entry_data_s entry_data;
ngx_stream_geoip2_ctx_t *geoip2 = (ngx_stream_geoip2_ctx_t *) data;
ngx_stream_geoip2_db_t *database = geoip2->database;
#if (NGX_HAVE_INET6)
uint8_t address[16], *addressp = address;
#else
unsigned long address;
#endif
if (geoip2->source.value.len > 0) {
if (ngx_stream_complex_value(s, &geoip2->source, &val) != NGX_OK) {
goto not_found;
}
if (ngx_parse_addr(s->connection->pool, &addr, val.data, val.len) != NGX_OK) {
goto not_found;
}
} else {
addr.sockaddr = s->connection->sockaddr;
addr.socklen = s->connection->socklen;
}
switch (addr.sockaddr->sa_family) {
case AF_INET:
#if (NGX_HAVE_INET6)
ngx_memset(addressp, 0, 12);
ngx_memcpy(addressp + 12, &((struct sockaddr_in *)
addr.sockaddr)->sin_addr.s_addr, 4);
break;
case AF_INET6:
ngx_memcpy(addressp, &((struct sockaddr_in6 *)
addr.sockaddr)->sin6_addr.s6_addr, 16);
#else
address = ((struct sockaddr_in *)addr.sockaddr)->sin_addr.s_addr;
#endif
break;
default:
goto not_found;
}
#if (NGX_HAVE_INET6)
if (ngx_memcmp(&address, &database->address, sizeof(address)) != 0) {
#else
if (address != database->address) {
#endif
memcpy(&database->address, &address, sizeof(address));
database->result = MMDB_lookup_sockaddr(&database->mmdb,
addr.sockaddr, &mmdb_error);
if (mmdb_error != MMDB_SUCCESS) {
goto not_found;
}
}
if (!database->result.found_entry
|| MMDB_aget_value(&database->result.entry, &entry_data, geoip2->lookup)
!= MMDB_SUCCESS)
{
goto not_found;
}
if (!entry_data.has_data) {
goto not_found;
}
switch (entry_data.type) {
case MMDB_DATA_TYPE_BOOLEAN:
FORMAT("%d", entry_data.boolean);
break;
case MMDB_DATA_TYPE_UTF8_STRING:
v->len = entry_data.data_size;
v->data = ngx_pnalloc(s->connection->pool, v->len);
if (v->data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(v->data, (u_char *) entry_data.utf8_string, v->len);
break;
case MMDB_DATA_TYPE_BYTES:
v->len = entry_data.data_size;
v->data = ngx_pnalloc(s->connection->pool, v->len);
if (v->data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(v->data, (u_char *) entry_data.bytes, v->len);
break;
case MMDB_DATA_TYPE_FLOAT:
FORMAT("%.5f", entry_data.float_value);
break;
case MMDB_DATA_TYPE_DOUBLE:
FORMAT("%.5f", entry_data.double_value);
break;
case MMDB_DATA_TYPE_UINT16:
FORMAT("%uD", entry_data.uint16);
break;
case MMDB_DATA_TYPE_UINT32:
FORMAT("%uD", entry_data.uint32);
break;
case MMDB_DATA_TYPE_INT32:
FORMAT("%D", entry_data.int32);
break;
case MMDB_DATA_TYPE_UINT64:
FORMAT("%uL", entry_data.uint64);
break;
case MMDB_DATA_TYPE_UINT128: ;
#if MMDB_UINT128_IS_BYTE_ARRAY
uint8_t *val = (uint8_t *) entry_data.uint128;
FORMAT("0x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x",
val[0], val[1], val[2], val[3],
val[4], val[5], val[6], val[7],
val[8], val[9], val[10], val[11],
val[12], val[13], val[14], val[15]);
#else
mmdb_uint128_t val = entry_data.uint128;
FORMAT("0x%016uxL%016uxL",
(uint64_t) (val >> 64), (uint64_t) val);
#endif
break;
default:
goto not_found;
}
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
return NGX_OK;
not_found:
if (geoip2->default_value.len > 0) {
v->data = geoip2->default_value.data;
v->len = geoip2->default_value.len;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
return NGX_OK;
}
v->not_found = 1;
return NGX_OK;
}
static ngx_int_t
ngx_stream_geoip2_metadata(ngx_stream_session_t *s, ngx_stream_variable_value_t *v,
uintptr_t data)
{
ngx_stream_geoip2_metadata_t *metadata = (ngx_stream_geoip2_metadata_t *) data;
ngx_stream_geoip2_db_t *database = metadata->database;
u_char *p;
if (ngx_strncmp(metadata->metavalue.data, "build_epoch", 11) == 0) {
FORMAT("%uL", database->mmdb.metadata.build_epoch);
} else if (ngx_strncmp(metadata->metavalue.data, "last_check", 10) == 0) {
FORMAT("%T", database->last_check);
} else if (ngx_strncmp(metadata->metavalue.data, "last_change", 11) == 0) {
FORMAT("%T", database->last_change);
} else {
v->not_found = 1;
return NGX_OK;
}
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
return NGX_OK;
}
static void *
ngx_stream_geoip2_create_conf(ngx_conf_t *cf)
{
ngx_pool_cleanup_t *cln;
ngx_stream_geoip2_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip2_conf_t));
if (conf == NULL) {
return NULL;
}
cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
return NULL;
}
ngx_queue_init(&conf->databases);
cln->handler = ngx_stream_geoip2_cleanup;
cln->data = conf;
return conf;
}
static char *
ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
int status;
char *rv;
ngx_str_t *value;
ngx_conf_t save;
ngx_stream_geoip2_db_t *database;
ngx_stream_geoip2_conf_t *gcf = conf;
ngx_queue_t *q;
value = cf->args->elts;
if (value[1].data && value[1].data[0] != '/') {
if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
if (!ngx_queue_empty(&gcf->databases)) {
for (q = ngx_queue_head(&gcf->databases);
q != ngx_queue_sentinel(&gcf->databases);
q = ngx_queue_next(q))
{
database = ngx_queue_data(q, ngx_stream_geoip2_db_t, queue);
if (ngx_strcmp(value[1].data, database->mmdb.filename) == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"Duplicate GeoIP2 mmdb - %V", &value[1]);
return NGX_CONF_ERROR;
}
}
}
database = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip2_db_t));
if (database == NULL) {
return NGX_CONF_ERROR;
}
ngx_queue_insert_tail(&gcf->databases, &database->queue);
database->last_check = database->last_change = ngx_time();
status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb);
if (status != MMDB_SUCCESS) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"MMDB_open(\"%V\") failed - %s", &value[1],
MMDB_strerror(status));
return NGX_CONF_ERROR;
}
save = *cf;
cf->handler = ngx_stream_geoip2_parse_config;
cf->handler_conf = (void *) database;
rv = ngx_conf_parse(cf, NULL);
*cf = save;
return rv;
}
static char *
ngx_stream_geoip2_parse_config(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{
ngx_stream_geoip2_db_t *database;
ngx_str_t *value;
time_t interval;
value = cf->args->elts;
if (value[0].data[0] == '$') {
return ngx_stream_geoip2_add_variable(cf, dummy, conf);
}
if (value[0].len == 11
&& ngx_strncmp(value[0].data, "auto_reload", 11) == 0) {
if ((int) cf->args->nelts != 2) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number of arguments for auto_reload");
return NGX_CONF_ERROR;
}
interval = ngx_parse_time(&value[1], true);
if (interval == (time_t) NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid interval for auto_reload \"%V\"",
value[1]);
return NGX_CONF_ERROR;
}
database = (ngx_stream_geoip2_db_t *) conf;
database->check_interval = interval;
return NGX_CONF_OK;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid setting \"%V\"", &value[0]);
return NGX_CONF_ERROR;
}
static char *
ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{
ngx_stream_geoip2_db_t *database;
ngx_str_t *value;
int nelts;
value = cf->args->elts;
if (value[0].data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[0]);
return NGX_CONF_ERROR;
}
value[0].len--;
value[0].data++;
nelts = (int) cf->args->nelts;
database = (ngx_stream_geoip2_db_t *) conf;
if (nelts > 0 && value[1].len == 8 && ngx_strncmp(value[1].data, "metadata", 8) == 0) {
return ngx_stream_geoip2_add_variable_metadata(cf, database);
}
return ngx_stream_geoip2_add_variable_geodata(cf, database);
}
static char *
ngx_stream_geoip2_add_variable_metadata(ngx_conf_t *cf, ngx_stream_geoip2_db_t *database)
{
ngx_stream_geoip2_metadata_t *metadata;
ngx_str_t *value, name;
ngx_stream_variable_t *var;
metadata = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip2_metadata_t));
if (metadata == NULL) {
return NGX_CONF_ERROR;
}
value = cf->args->elts;
name = value[0];
metadata->database = database;
metadata->metavalue = value[2];
var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
if (var == NULL) {
return NGX_CONF_ERROR;
}
var->get_handler = ngx_stream_geoip2_metadata;
var->data = (uintptr_t) metadata;
return NGX_CONF_OK;
}
static char *
ngx_stream_geoip2_add_variable_geodata(ngx_conf_t *cf, ngx_stream_geoip2_db_t *database)
{
ngx_stream_geoip2_ctx_t *geoip2;
ngx_stream_compile_complex_value_t ccv;
ngx_str_t *value, name, source;
ngx_stream_variable_t *var;
int i, nelts, idx;
geoip2 = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip2_ctx_t));
if (geoip2 == NULL) {
return NGX_CONF_ERROR;
}
geoip2->database = database;
ngx_str_null(&source);
value = cf->args->elts;
name = value[0];
nelts = (int) cf->args->nelts;
idx = 1;
if (nelts > idx) {
for (i = idx; i < nelts; i++) {
if (ngx_strnstr(value[idx].data, "=", value[idx].len) == NULL) {
break;
}
if (value[idx].len > 8 && ngx_strncmp(value[idx].data, "default=", 8) == 0) {
if (geoip2->default_value.len > 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"default has already been declared for \"$%V\"", &name);
return NGX_CONF_ERROR;
}
geoip2->default_value.len = value[idx].len - 8;
geoip2->default_value.data = value[idx].data + 8;
} else if (value[idx].len > 7 && ngx_strncmp(value[idx].data, "source=", 7) == 0) {
if (source.len > 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"source has already been declared for \"$%V\"", &name);
return NGX_CONF_ERROR;
}
source.len = value[idx].len - 7;
source.data = value[idx].data + 7;
if (source.data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid source variable name \"%V\"", &source);
return NGX_CONF_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &source;
ccv.complex_value = &geoip2->source;
if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"unable to compile \"%V\" for \"$%V\"", &source, &name);
return NGX_CONF_ERROR;
}
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid setting \"%V\" for \"$%V\"", &value[idx], &name);
return NGX_CONF_ERROR;
}
idx++;
}
}
var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
if (var == NULL) {
return NGX_CONF_ERROR;
}
geoip2->lookup = ngx_pcalloc(cf->pool,
sizeof(const char *) * (cf->args->nelts - (idx - 1)));
if (geoip2->lookup == NULL) {
return NGX_CONF_ERROR;
}
for (i = idx; i < nelts; i++) {
geoip2->lookup[i - idx] = (char *) value[i].data;
}
geoip2->lookup[i - idx] = NULL;
var->get_handler = ngx_stream_geoip2_variable;
var->data = (uintptr_t) geoip2;
return NGX_CONF_OK;
}
static void
ngx_stream_geoip2_cleanup(void *data)
{
ngx_queue_t *q;
ngx_stream_geoip2_db_t *database;
ngx_stream_geoip2_conf_t *gcf = data;
while (!ngx_queue_empty(&gcf->databases)) {
q = ngx_queue_head(&gcf->databases);
ngx_queue_remove(q);
database = ngx_queue_data(q, ngx_stream_geoip2_db_t, queue);
MMDB_close(&database->mmdb);
}
}
static ngx_int_t
ngx_stream_geoip2_log_handler(ngx_stream_session_t *s)
{
int status;
MMDB_s tmpdb;
ngx_queue_t *q;
ngx_file_info_t fi;
ngx_stream_geoip2_db_t *database;
ngx_stream_geoip2_conf_t *gcf;
ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
"geoip2 stream log handler");
gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip2_module);
if (ngx_queue_empty(&gcf->databases)) {
return NGX_OK;
}
for (q = ngx_queue_head(&gcf->databases);
q != ngx_queue_sentinel(&gcf->databases);
q = ngx_queue_next(q))
{
database = ngx_queue_data(q, ngx_stream_geoip2_db_t, queue);
if (database->check_interval == 0) {
continue;
}
if ((database->last_check + database->check_interval)
> ngx_time())
{
continue;
}
database->last_check = ngx_time();
if (ngx_file_info(database->mmdb.filename, &fi) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, s->connection->log, ngx_errno,
ngx_file_info_n " \"%s\" failed",
database->mmdb.filename);
continue;
}
if (ngx_file_mtime(&fi) <= database->last_change) {
continue;
}
/* do the reload */
ngx_memzero(&tmpdb, sizeof(MMDB_s));
status = MMDB_open(database->mmdb.filename, MMDB_MODE_MMAP, &tmpdb);
if (status != MMDB_SUCCESS) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"MMDB_open(\"%s\") failed to reload - %s",
database->mmdb.filename, MMDB_strerror(status));
continue;
}
database->last_change = ngx_file_mtime(&fi);
MMDB_close(&database->mmdb);
database->mmdb = tmpdb;
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
"Reload MMDB \"%s\"",
database->mmdb.filename);
}
return NGX_OK;
}
static ngx_int_t
ngx_stream_geoip2_init(ngx_conf_t *cf)
{
ngx_stream_handler_pt *h;
ngx_stream_core_main_conf_t *cmcf;
cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
h = ngx_array_push(&cmcf->phases[NGX_STREAM_LOG_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_stream_geoip2_log_handler;
return NGX_OK;
}