From 39a8abfa6024e5324bb85ab4dd7351b8f7905892 Mon Sep 17 00:00:00 2001 From: Lee Valentine Date: Fri, 29 Jun 2018 21:20:55 +0100 Subject: [PATCH] Add support for auto reloading the database if it has changed --- README.md | 13 ++++- ngx_http_geoip2_module.c | 96 ++++++++++++++++++++++++++++++++++++- ngx_stream_geoip2_module.c | 98 +++++++++++++++++++++++++++++++++++++- 3 files changed, 204 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9cf430d..7af7690 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ The free GeoLite2 databases are available from [Maxminds website](http://dev.max 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; @@ -78,7 +79,17 @@ Retrieve metadata regarding the geoip database. ``` $variable_name metadata ``` -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 +``` ##### GeoIP: ``` diff --git a/ngx_http_geoip2_module.c b/ngx_http_geoip2_module.c index b64afee..c28eb00 100644 --- a/ngx_http_geoip2_module.c +++ b/ngx_http_geoip2_module.c @@ -15,6 +15,9 @@ 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 @@ -41,6 +44,8 @@ typedef struct { } 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, ngx_http_variable_value_t *v, uintptr_t data); 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(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, @@ -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 ngx_http_geoip2_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, 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; #endif + ngx_http_geoip2_reload(database, r->connection->log); + if (geoip2->source.value.len > 0) { if (ngx_http_complex_value(r, &geoip2->source, &val) != NGX_OK) { 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; u_char *p; + ngx_http_geoip2_reload(database, r->connection->log); + 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; @@ -376,6 +426,8 @@ ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } + database->last_check = database->last_change = ngx_time(); + status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb); if (status != MMDB_SUCCESS) { @@ -392,7 +444,7 @@ ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif save = *cf; - cf->handler = ngx_http_geoip2_add_variable; + cf->handler = ngx_http_geoip2_parse_config; cf->handler_conf = (void *) database; 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 * ngx_http_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) { diff --git a/ngx_stream_geoip2_module.c b/ngx_stream_geoip2_module.c index 4952b45..b9d0260 100644 --- a/ngx_stream_geoip2_module.c +++ b/ngx_stream_geoip2_module.c @@ -16,6 +16,9 @@ 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 @@ -40,11 +43,17 @@ typedef struct { } 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, 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, @@ -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 ngx_stream_geoip2_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) @@ -124,6 +168,8 @@ ngx_stream_geoip2_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t unsigned long address; #endif + ngx_stream_geoip2_reload(database, s->connection->log); + if (geoip2->source.value.len > 0) { if (ngx_stream_complex_value(s, &geoip2->source, &val) != NGX_OK) { 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; u_char *p; + ngx_stream_geoip2_reload(database, s->connection->log); + 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; @@ -343,6 +395,8 @@ ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } + database->last_check = database->last_change = ngx_time(); + status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb); if (status != MMDB_SUCCESS) { @@ -359,7 +413,7 @@ ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif save = *cf; - cf->handler = ngx_stream_geoip2_add_variable; + cf->handler = ngx_stream_geoip2_parse_config; cf->handler_conf = (void *) database; 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 * ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) {