Add support for auto reloading the database if it has changed

This commit is contained in:
Lee Valentine 2018-06-29 21:20:55 +01:00
parent 7eaa956e61
commit 39a8abfa60
3 changed files with 204 additions and 3 deletions

View file

@ -48,6 +48,7 @@ The free GeoLite2 databases are available from [Maxminds website](http://dev.max
http { http {
... ...
geoip2 /etc/maxmind-country.mmdb { geoip2 /etc/maxmind-country.mmdb {
auto_reload 5m;
$geoip2_metadata_country_build metadata build_epoch; $geoip2_metadata_country_build metadata build_epoch;
$geoip2_data_country_code default=US source=$variable_with_ip country iso_code; $geoip2_data_country_code default=US source=$variable_with_ip country iso_code;
$geoip2_data_country_name country names en; $geoip2_data_country_name country names en;
@ -78,7 +79,17 @@ Retrieve metadata regarding the geoip database.
``` ```
$variable_name metadata <field> $variable_name metadata <field>
``` ```
Currently the only metadata field supported is build_epoch. 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_reload: 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: ##### GeoIP:
``` ```

View file

@ -15,6 +15,9 @@
typedef struct { typedef struct {
MMDB_s mmdb; MMDB_s mmdb;
MMDB_lookup_result_s result; MMDB_lookup_result_s result;
time_t last_check;
time_t last_change;
time_t check_interval;
#if (NGX_HAVE_INET6) #if (NGX_HAVE_INET6)
uint8_t address[16]; uint8_t address[16];
#else #else
@ -41,6 +44,8 @@ typedef struct {
} ngx_http_geoip2_metadata_t; } ngx_http_geoip2_metadata_t;
static ngx_int_t ngx_http_geoip2_reload(ngx_http_geoip2_db_t *database,
ngx_log_t *log);
static ngx_int_t ngx_http_geoip2_variable(ngx_http_request_t *r, static ngx_int_t ngx_http_geoip2_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data); ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_geoip2_metadata(ngx_http_request_t *r, static ngx_int_t ngx_http_geoip2_metadata(ngx_http_request_t *r,
@ -49,6 +54,8 @@ 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_init_conf(ngx_conf_t *cf, void *conf);
static char *ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, static char *ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf); 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, static char *ngx_http_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy,
void *conf); void *conf);
static char *ngx_http_geoip2_add_variable_geodata(ngx_conf_t *cf, static char *ngx_http_geoip2_add_variable_geodata(ngx_conf_t *cf,
@ -129,6 +136,41 @@ ngx_module_t ngx_http_geoip2_module = {
}; };
static ngx_int_t
ngx_http_geoip2_reload(ngx_http_geoip2_db_t *database, ngx_log_t *log)
{
struct stat attr;
MMDB_s tmpdb;
int status;
if (database->check_interval > 0
&& database->last_check + database->check_interval <= ngx_time()) {
database->last_check = ngx_time();
stat(database->mmdb.filename, &attr);
if (attr.st_mtime > database->last_change) {
status = MMDB_open(database->mmdb.filename, MMDB_MODE_MMAP, &tmpdb);
if (status != MMDB_SUCCESS) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"MMDB_open(\"%s\") failed to reload - %s",
database->mmdb.filename, MMDB_strerror(status));
return NGX_ERROR;
}
database->last_change = attr.st_mtime;
MMDB_close(&database->mmdb);
database->mmdb = tmpdb;
ngx_log_error(NGX_LOG_INFO, log, 0, "Reload MMDB \"%s\"",
tmpdb.filename);
}
}
return NGX_OK;
}
static ngx_int_t static ngx_int_t
ngx_http_geoip2_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, ngx_http_geoip2_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
uintptr_t data) uintptr_t data)
@ -149,6 +191,8 @@ ngx_http_geoip2_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
unsigned long address; unsigned long address;
#endif #endif
ngx_http_geoip2_reload(database, r->connection->log);
if (geoip2->source.value.len > 0) { if (geoip2->source.value.len > 0) {
if (ngx_http_complex_value(r, &geoip2->source, &val) != NGX_OK) { if (ngx_http_complex_value(r, &geoip2->source, &val) != NGX_OK) {
goto not_found; goto not_found;
@ -294,8 +338,14 @@ ngx_http_geoip2_metadata(ngx_http_request_t *r, ngx_http_variable_value_t *v,
ngx_http_geoip2_db_t *database = metadata->database; ngx_http_geoip2_db_t *database = metadata->database;
u_char *p; u_char *p;
ngx_http_geoip2_reload(database, r->connection->log);
if (ngx_strncmp(metadata->metavalue.data, "build_epoch", 11) == 0) { if (ngx_strncmp(metadata->metavalue.data, "build_epoch", 11) == 0) {
FORMAT("%uL", database->mmdb.metadata.build_epoch); 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 { } else {
v->not_found = 1; v->not_found = 1;
return NGX_OK; return NGX_OK;
@ -376,6 +426,8 @@ ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
return NGX_CONF_ERROR; return NGX_CONF_ERROR;
} }
database->last_check = database->last_change = ngx_time();
status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb); status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb);
if (status != MMDB_SUCCESS) { if (status != MMDB_SUCCESS) {
@ -392,7 +444,7 @@ ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif #endif
save = *cf; save = *cf;
cf->handler = ngx_http_geoip2_add_variable; cf->handler = ngx_http_geoip2_parse_config;
cf->handler_conf = (void *) database; cf->handler_conf = (void *) database;
rv = ngx_conf_parse(cf, NULL); rv = ngx_conf_parse(cf, NULL);
@ -401,6 +453,48 @@ 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)
{
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 * static char *
ngx_http_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) ngx_http_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{ {

View file

@ -16,6 +16,9 @@
typedef struct { typedef struct {
MMDB_s mmdb; MMDB_s mmdb;
MMDB_lookup_result_s result; MMDB_lookup_result_s result;
time_t last_check;
time_t last_change;
time_t check_interval;
#if (NGX_HAVE_INET6) #if (NGX_HAVE_INET6)
uint8_t address[16]; uint8_t address[16];
#else #else
@ -40,11 +43,17 @@ typedef struct {
} ngx_stream_geoip2_metadata_t; } ngx_stream_geoip2_metadata_t;
static ngx_int_t ngx_stream_geoip2_reload(ngx_stream_geoip2_db_t *database,
ngx_log_t *log);
static ngx_int_t ngx_stream_geoip2_variable(ngx_stream_session_t *s, static ngx_int_t ngx_stream_geoip2_variable(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data); ngx_stream_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_stream_geoip2_metadata(ngx_stream_session_t *s, static ngx_int_t ngx_stream_geoip2_metadata(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data); ngx_stream_variable_value_t *v, uintptr_t data);
static void *ngx_stream_geoip2_create_conf(ngx_conf_t *cf); 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, static char *ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf); void *conf);
static char *ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, static char *ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy,
@ -106,6 +115,41 @@ ngx_module_t ngx_stream_geoip2_module = {
}; };
static ngx_int_t
ngx_stream_geoip2_reload(ngx_stream_geoip2_db_t *database, ngx_log_t *log)
{
struct stat attr;
MMDB_s tmpdb;
int status;
if (database->check_interval > 0
&& database->last_check + database->check_interval <= ngx_time()) {
database->last_check = ngx_time();
stat(database->mmdb.filename, &attr);
if (attr.st_mtime > database->last_change) {
status = MMDB_open(database->mmdb.filename, MMDB_MODE_MMAP, &tmpdb);
if (status != MMDB_SUCCESS) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"MMDB_open(\"%s\") failed to reload - %s",
database->mmdb.filename, MMDB_strerror(status));
return NGX_ERROR;
}
database->last_change = attr.st_mtime;
MMDB_close(&database->mmdb);
database->mmdb = tmpdb;
ngx_log_error(NGX_LOG_INFO, log, 0, "Reload MMDB \"%s\"",
tmpdb.filename);
}
}
return NGX_OK;
}
static ngx_int_t static ngx_int_t
ngx_stream_geoip2_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, ngx_stream_geoip2_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v,
uintptr_t data) uintptr_t data)
@ -124,6 +168,8 @@ ngx_stream_geoip2_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t
unsigned long address; unsigned long address;
#endif #endif
ngx_stream_geoip2_reload(database, s->connection->log);
if (geoip2->source.value.len > 0) { if (geoip2->source.value.len > 0) {
if (ngx_stream_complex_value(s, &geoip2->source, &val) != NGX_OK) { if (ngx_stream_complex_value(s, &geoip2->source, &val) != NGX_OK) {
goto not_found; goto not_found;
@ -263,8 +309,14 @@ ngx_stream_geoip2_metadata(ngx_stream_session_t *s, ngx_stream_variable_value_t
ngx_stream_geoip2_db_t *database = metadata->database; ngx_stream_geoip2_db_t *database = metadata->database;
u_char *p; u_char *p;
ngx_stream_geoip2_reload(database, s->connection->log);
if (ngx_strncmp(metadata->metavalue.data, "build_epoch", 11) == 0) { if (ngx_strncmp(metadata->metavalue.data, "build_epoch", 11) == 0) {
FORMAT("%uL", database->mmdb.metadata.build_epoch); 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 { } else {
v->not_found = 1; v->not_found = 1;
return NGX_OK; return NGX_OK;
@ -343,6 +395,8 @@ ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
return NGX_CONF_ERROR; return NGX_CONF_ERROR;
} }
database->last_check = database->last_change = ngx_time();
status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb); status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb);
if (status != MMDB_SUCCESS) { if (status != MMDB_SUCCESS) {
@ -359,7 +413,7 @@ ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif #endif
save = *cf; save = *cf;
cf->handler = ngx_stream_geoip2_add_variable; cf->handler = ngx_stream_geoip2_parse_config;
cf->handler_conf = (void *) database; cf->handler_conf = (void *) database;
rv = ngx_conf_parse(cf, NULL); rv = ngx_conf_parse(cf, NULL);
@ -368,6 +422,48 @@ 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)
{
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 * static char *
ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{ {