├── config ├── LICENSE ├── README.md ├── ngx_stream_geoip2_module.c └── ngx_http_geoip2_module.c /config: -------------------------------------------------------------------------------- 1 | ngx_feature="MaxmindDB library" 2 | ngx_feature_name= 3 | ngx_feature_run=no 4 | ngx_feature_incs="#include " 5 | ngx_feature_libs=-lmaxminddb 6 | ngx_feature_test="MMDB_s mmdb" 7 | . auto/feature 8 | 9 | ngx_addon_name="ngx_geoip2_module" 10 | 11 | if [ $ngx_found = yes ]; then 12 | if test -n "$ngx_module_link"; then 13 | if [ $HTTP != NO ]; then 14 | ngx_module_type=HTTP 15 | ngx_module_name="ngx_http_geoip2_module" 16 | ngx_module_incs= 17 | ngx_module_deps= 18 | ngx_module_srcs="$ngx_addon_dir/ngx_http_geoip2_module.c" 19 | ngx_module_libs="$ngx_feature_libs" 20 | . auto/module 21 | fi 22 | 23 | nginx_version=`awk '/^#define nginx_version / {print $3}' src/core/nginx.h` 24 | if [ $STREAM != NO -a $nginx_version -gt 1011001 ]; then 25 | ngx_module_type=STREAM 26 | ngx_module_name="ngx_stream_geoip2_module" 27 | ngx_module_incs= 28 | ngx_module_deps= 29 | ngx_module_srcs="$ngx_addon_dir/ngx_stream_geoip2_module.c" 30 | ngx_module_libs="$ngx_feature_libs" 31 | . auto/module 32 | fi 33 | else 34 | HTTP_MODULES="$HTTP_MODULES ngx_http_geoip2_module" 35 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_geoip2_module.c" 36 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs" 37 | fi 38 | else 39 | cat << END 40 | $0: error: the geoip2 module requires the maxminddb library. 41 | END 42 | exit 1 43 | fi 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Lee Valentine 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Description 2 | =========== 3 | 4 | **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) 5 | 6 | The module now supports nginx streams and can be used in the same way the http module can be used. 7 | 8 | ## Installing 9 | First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as described in its [README.md 10 | file](https://github.com/maxmind/libmaxminddb/blob/main/README.md#installing-from-a-tarball). 11 | 12 | #### Download nginx source 13 | ``` 14 | wget http://nginx.org/download/nginx-VERSION.tar.gz 15 | tar zxvf nginx-VERSION.tar.gz 16 | cd nginx-VERSION 17 | ``` 18 | 19 | ##### To build as a dynamic module (nginx 1.9.11+): 20 | ``` 21 | ./configure --with-compat --add-dynamic-module=/path/to/ngx_http_geoip2_module 22 | make modules 23 | ``` 24 | 25 | This will produce ```objs/ngx_http_geoip2_module.so```. It can be copied to your nginx module path manually if you wish. 26 | 27 | Add the following line to your nginx.conf: 28 | ``` 29 | load_module modules/ngx_http_geoip2_module.so; 30 | ``` 31 | 32 | ##### To build as a static module: 33 | ``` 34 | ./configure --add-module=/path/to/ngx_http_geoip2_module 35 | make 36 | make install 37 | ``` 38 | 39 | ##### If you need stream support, make sure to compile with stream: 40 | ``` 41 | ./configure --add-dynamic-module=/path/to/ngx_http_geoip2_module --with-stream 42 | OR 43 | ./configure --add-module=/path/to/ngx_http_geoip2_module --with-stream 44 | ``` 45 | 46 | 47 | ## Download Maxmind GeoLite2 Database (optional) 48 | The free GeoLite2 databases are available from [Maxminds website](http://dev.maxmind.com/geoip/geoip2/geolite2/) (requires signing up) 49 | 50 | ## Example Usage: 51 | ``` 52 | http { 53 | ... 54 | geoip2 /etc/maxmind-country.mmdb { 55 | auto_reload 5m; 56 | $geoip2_metadata_country_build metadata build_epoch; 57 | $geoip2_data_country_code default=US source=$variable_with_ip country iso_code; 58 | $geoip2_data_country_name country names en; 59 | } 60 | 61 | geoip2 /etc/maxmind-city.mmdb { 62 | $geoip2_data_city_name default=London city names en; 63 | } 64 | .... 65 | 66 | fastcgi_param COUNTRY_CODE $geoip2_data_country_code; 67 | fastcgi_param COUNTRY_NAME $geoip2_data_country_name; 68 | fastcgi_param CITY_NAME $geoip2_data_city_name; 69 | .... 70 | } 71 | 72 | stream { 73 | ... 74 | geoip2 /etc/maxmind-country.mmdb { 75 | $geoip2_data_country_code default=US source=$remote_addr country iso_code; 76 | } 77 | ... 78 | } 79 | ``` 80 | 81 | ##### Metadata: 82 | Retrieve metadata regarding the geoip database. 83 | ``` 84 | $variable_name metadata 85 | ``` 86 | Available fields: 87 | - build_epoch: the build timestamp of the maxmind database. 88 | - last_check: the last time the database was checked for changes (when using auto_reload) 89 | - last_change: the last time the database was reloaded (when using auto_reload) 90 | 91 | ##### Autoreload (default: disabled): 92 | Enabling auto reload will have nginx check the modification time of the database at the specified 93 | interval and reload it if it has changed. 94 | ``` 95 | auto_reload 96 | ``` 97 | 98 | ##### GeoIP: 99 | ``` 100 | $variable_name [default= 116 | "iso_code": 117 | "US" 118 | "names": 119 | { 120 | "de": 121 | "USA" 122 | "en": 123 | "United States" 124 | } 125 | } 126 | } 127 | 128 | $ mmdblookup --file /usr/share/GeoIP/GeoIP2-Country.mmdb --ip 8.8.8.8 country names en 129 | 130 | "United States" 131 | ``` 132 | 133 | This translates to: 134 | 135 | ``` 136 | $country_name "default=United States" source=$remote_addr country names en 137 | ``` 138 | 139 | ##### Additional Commands: 140 | 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. 141 | 142 | However, if you provide the `source=$variable_with_ip` option on a variable, these settings will be ignored for that particular variable. 143 | 144 | ``` 145 | geoip2_proxy < cidr > 146 | ``` 147 | 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. 148 | 149 | ``` 150 | geoip2_proxy_recursive < on | off > 151 | ``` 152 | 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. 153 | -------------------------------------------------------------------------------- /ngx_stream_geoip2_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Lee Valentine 3 | * Copyright (C) Andrei Belov 4 | * 5 | * Based on nginx's 'ngx_stream_geoip_module.c' by Igor Sysoev 6 | */ 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | 16 | typedef struct { 17 | MMDB_s mmdb; 18 | MMDB_lookup_result_s result; 19 | time_t last_check; 20 | time_t last_change; 21 | time_t check_interval; 22 | #if (NGX_HAVE_INET6) 23 | uint8_t address[16]; 24 | #else 25 | unsigned long address; 26 | #endif 27 | ngx_queue_t queue; 28 | } ngx_stream_geoip2_db_t; 29 | 30 | typedef struct { 31 | ngx_queue_t databases; 32 | } ngx_stream_geoip2_conf_t; 33 | 34 | typedef struct { 35 | ngx_stream_geoip2_db_t *database; 36 | const char **lookup; 37 | ngx_str_t default_value; 38 | ngx_stream_complex_value_t source; 39 | } ngx_stream_geoip2_ctx_t; 40 | 41 | typedef struct { 42 | ngx_stream_geoip2_db_t *database; 43 | ngx_str_t metavalue; 44 | } ngx_stream_geoip2_metadata_t; 45 | 46 | 47 | static ngx_int_t ngx_stream_geoip2_variable(ngx_stream_session_t *s, 48 | ngx_stream_variable_value_t *v, uintptr_t data); 49 | static ngx_int_t ngx_stream_geoip2_metadata(ngx_stream_session_t *s, 50 | ngx_stream_variable_value_t *v, uintptr_t data); 51 | static void *ngx_stream_geoip2_create_conf(ngx_conf_t *cf); 52 | static char *ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, 53 | void *conf); 54 | static char *ngx_stream_geoip2_parse_config(ngx_conf_t *cf, ngx_command_t *dummy, 55 | void *conf); 56 | static char *ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, 57 | void *conf); 58 | static char *ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, 59 | void *conf); 60 | static char *ngx_stream_geoip2_add_variable_geodata(ngx_conf_t *cf, 61 | ngx_stream_geoip2_db_t *database); 62 | static char *ngx_stream_geoip2_add_variable_metadata(ngx_conf_t *cf, 63 | ngx_stream_geoip2_db_t *database); 64 | static void ngx_stream_geoip2_cleanup(void *data); 65 | static ngx_int_t ngx_stream_geoip2_init(ngx_conf_t *cf); 66 | 67 | 68 | #define FORMAT(fmt, ...) do { \ 69 | p = ngx_palloc(s->connection->pool, NGX_OFF_T_LEN); \ 70 | if (p == NULL) { \ 71 | return NGX_ERROR; \ 72 | } \ 73 | v->len = ngx_sprintf(p, fmt, __VA_ARGS__) - p; \ 74 | v->data = p; \ 75 | } while (0) 76 | 77 | static ngx_command_t ngx_stream_geoip2_commands[] = { 78 | 79 | { ngx_string("geoip2"), 80 | NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, 81 | ngx_stream_geoip2, 82 | NGX_STREAM_MAIN_CONF_OFFSET, 83 | 0, 84 | NULL }, 85 | 86 | ngx_null_command 87 | }; 88 | 89 | 90 | static ngx_stream_module_t ngx_stream_geoip2_module_ctx = { 91 | NULL, /* preconfiguration */ 92 | ngx_stream_geoip2_init, /* postconfiguration */ 93 | 94 | ngx_stream_geoip2_create_conf, /* create main configuration */ 95 | NULL, /* init main configuration */ 96 | 97 | NULL, /* create server configuration */ 98 | NULL /* merge server configuration */ 99 | }; 100 | 101 | 102 | ngx_module_t ngx_stream_geoip2_module = { 103 | NGX_MODULE_V1, 104 | &ngx_stream_geoip2_module_ctx, /* module context */ 105 | ngx_stream_geoip2_commands, /* module directives */ 106 | NGX_STREAM_MODULE, /* module type */ 107 | NULL, /* init master */ 108 | NULL, /* init module */ 109 | NULL, /* init process */ 110 | NULL, /* init thread */ 111 | NULL, /* exit thread */ 112 | NULL, /* exit process */ 113 | NULL, /* exit master */ 114 | NGX_MODULE_V1_PADDING 115 | }; 116 | 117 | 118 | static ngx_int_t 119 | ngx_stream_geoip2_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, 120 | uintptr_t data) 121 | { 122 | int mmdb_error; 123 | u_char *p; 124 | ngx_str_t val; 125 | ngx_addr_t addr; 126 | MMDB_entry_data_s entry_data; 127 | ngx_stream_geoip2_ctx_t *geoip2 = (ngx_stream_geoip2_ctx_t *) data; 128 | ngx_stream_geoip2_db_t *database = geoip2->database; 129 | 130 | #if (NGX_HAVE_INET6) 131 | uint8_t address[16], *addressp = address; 132 | #else 133 | unsigned long address; 134 | #endif 135 | 136 | if (geoip2->source.value.len > 0) { 137 | if (ngx_stream_complex_value(s, &geoip2->source, &val) != NGX_OK) { 138 | goto not_found; 139 | } 140 | 141 | if (ngx_parse_addr(s->connection->pool, &addr, val.data, val.len) != NGX_OK) { 142 | goto not_found; 143 | } 144 | } else { 145 | addr.sockaddr = s->connection->sockaddr; 146 | addr.socklen = s->connection->socklen; 147 | } 148 | 149 | switch (addr.sockaddr->sa_family) { 150 | case AF_INET: 151 | #if (NGX_HAVE_INET6) 152 | ngx_memset(addressp, 0, 12); 153 | ngx_memcpy(addressp + 12, &((struct sockaddr_in *) 154 | addr.sockaddr)->sin_addr.s_addr, 4); 155 | break; 156 | 157 | case AF_INET6: 158 | ngx_memcpy(addressp, &((struct sockaddr_in6 *) 159 | addr.sockaddr)->sin6_addr.s6_addr, 16); 160 | #else 161 | address = ((struct sockaddr_in *)addr.sockaddr)->sin_addr.s_addr; 162 | #endif 163 | break; 164 | 165 | default: 166 | goto not_found; 167 | } 168 | 169 | #if (NGX_HAVE_INET6) 170 | if (ngx_memcmp(&address, &database->address, sizeof(address)) != 0) { 171 | #else 172 | if (address != database->address) { 173 | #endif 174 | memcpy(&database->address, &address, sizeof(address)); 175 | database->result = MMDB_lookup_sockaddr(&database->mmdb, 176 | addr.sockaddr, &mmdb_error); 177 | 178 | if (mmdb_error != MMDB_SUCCESS) { 179 | goto not_found; 180 | } 181 | } 182 | 183 | if (!database->result.found_entry 184 | || MMDB_aget_value(&database->result.entry, &entry_data, geoip2->lookup) 185 | != MMDB_SUCCESS) 186 | { 187 | goto not_found; 188 | } 189 | 190 | if (!entry_data.has_data) { 191 | goto not_found; 192 | } 193 | 194 | switch (entry_data.type) { 195 | case MMDB_DATA_TYPE_BOOLEAN: 196 | FORMAT("%d", entry_data.boolean); 197 | break; 198 | case MMDB_DATA_TYPE_UTF8_STRING: 199 | v->len = entry_data.data_size; 200 | v->data = ngx_pnalloc(s->connection->pool, v->len); 201 | if (v->data == NULL) { 202 | return NGX_ERROR; 203 | } 204 | ngx_memcpy(v->data, (u_char *) entry_data.utf8_string, v->len); 205 | break; 206 | case MMDB_DATA_TYPE_BYTES: 207 | v->len = entry_data.data_size; 208 | v->data = ngx_pnalloc(s->connection->pool, v->len); 209 | if (v->data == NULL) { 210 | return NGX_ERROR; 211 | } 212 | ngx_memcpy(v->data, (u_char *) entry_data.bytes, v->len); 213 | break; 214 | case MMDB_DATA_TYPE_FLOAT: 215 | FORMAT("%.5f", entry_data.float_value); 216 | break; 217 | case MMDB_DATA_TYPE_DOUBLE: 218 | FORMAT("%.5f", entry_data.double_value); 219 | break; 220 | case MMDB_DATA_TYPE_UINT16: 221 | FORMAT("%uD", entry_data.uint16); 222 | break; 223 | case MMDB_DATA_TYPE_UINT32: 224 | FORMAT("%uD", entry_data.uint32); 225 | break; 226 | case MMDB_DATA_TYPE_INT32: 227 | FORMAT("%D", entry_data.int32); 228 | break; 229 | case MMDB_DATA_TYPE_UINT64: 230 | FORMAT("%uL", entry_data.uint64); 231 | break; 232 | case MMDB_DATA_TYPE_UINT128: ; 233 | #if MMDB_UINT128_IS_BYTE_ARRAY 234 | uint8_t *val = (uint8_t *) entry_data.uint128; 235 | FORMAT("0x%02x%02x%02x%02x%02x%02x%02x%02x" 236 | "%02x%02x%02x%02x%02x%02x%02x%02x", 237 | val[0], val[1], val[2], val[3], 238 | val[4], val[5], val[6], val[7], 239 | val[8], val[9], val[10], val[11], 240 | val[12], val[13], val[14], val[15]); 241 | #else 242 | mmdb_uint128_t val = entry_data.uint128; 243 | FORMAT("0x%016uxL%016uxL", 244 | (uint64_t) (val >> 64), (uint64_t) val); 245 | #endif 246 | break; 247 | default: 248 | goto not_found; 249 | } 250 | 251 | v->valid = 1; 252 | v->no_cacheable = 0; 253 | v->not_found = 0; 254 | 255 | return NGX_OK; 256 | 257 | not_found: 258 | if (geoip2->default_value.len > 0) { 259 | v->data = geoip2->default_value.data; 260 | v->len = geoip2->default_value.len; 261 | 262 | v->valid = 1; 263 | v->no_cacheable = 0; 264 | v->not_found = 0; 265 | 266 | return NGX_OK; 267 | } 268 | 269 | v->not_found = 1; 270 | 271 | return NGX_OK; 272 | } 273 | 274 | 275 | static ngx_int_t 276 | ngx_stream_geoip2_metadata(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, 277 | uintptr_t data) 278 | { 279 | ngx_stream_geoip2_metadata_t *metadata = (ngx_stream_geoip2_metadata_t *) data; 280 | ngx_stream_geoip2_db_t *database = metadata->database; 281 | u_char *p; 282 | 283 | if (ngx_strncmp(metadata->metavalue.data, "build_epoch", 11) == 0) { 284 | FORMAT("%uL", database->mmdb.metadata.build_epoch); 285 | } else if (ngx_strncmp(metadata->metavalue.data, "last_check", 10) == 0) { 286 | FORMAT("%T", database->last_check); 287 | } else if (ngx_strncmp(metadata->metavalue.data, "last_change", 11) == 0) { 288 | FORMAT("%T", database->last_change); 289 | } else { 290 | v->not_found = 1; 291 | return NGX_OK; 292 | } 293 | 294 | v->valid = 1; 295 | v->no_cacheable = 0; 296 | v->not_found = 0; 297 | 298 | return NGX_OK; 299 | } 300 | 301 | 302 | static void * 303 | ngx_stream_geoip2_create_conf(ngx_conf_t *cf) 304 | { 305 | ngx_pool_cleanup_t *cln; 306 | ngx_stream_geoip2_conf_t *conf; 307 | 308 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip2_conf_t)); 309 | if (conf == NULL) { 310 | return NULL; 311 | } 312 | 313 | cln = ngx_pool_cleanup_add(cf->pool, 0); 314 | if (cln == NULL) { 315 | return NULL; 316 | } 317 | 318 | ngx_queue_init(&conf->databases); 319 | 320 | cln->handler = ngx_stream_geoip2_cleanup; 321 | cln->data = conf; 322 | 323 | return conf; 324 | } 325 | 326 | 327 | static char * 328 | ngx_stream_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 329 | { 330 | int status; 331 | char *rv; 332 | ngx_str_t *value; 333 | ngx_conf_t save; 334 | ngx_stream_geoip2_db_t *database; 335 | ngx_stream_geoip2_conf_t *gcf = conf; 336 | ngx_queue_t *q; 337 | 338 | value = cf->args->elts; 339 | 340 | if (value[1].data && value[1].data[0] != '/') { 341 | if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) { 342 | return NGX_CONF_ERROR; 343 | } 344 | } 345 | 346 | if (!ngx_queue_empty(&gcf->databases)) { 347 | for (q = ngx_queue_head(&gcf->databases); 348 | q != ngx_queue_sentinel(&gcf->databases); 349 | q = ngx_queue_next(q)) 350 | { 351 | database = ngx_queue_data(q, ngx_stream_geoip2_db_t, queue); 352 | if (ngx_strcmp(value[1].data, database->mmdb.filename) == 0) { 353 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 354 | "Duplicate GeoIP2 mmdb - %V", &value[1]); 355 | return NGX_CONF_ERROR; 356 | } 357 | } 358 | } 359 | 360 | database = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip2_db_t)); 361 | if (database == NULL) { 362 | return NGX_CONF_ERROR; 363 | } 364 | 365 | ngx_queue_insert_tail(&gcf->databases, &database->queue); 366 | database->last_check = database->last_change = ngx_time(); 367 | 368 | status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb); 369 | 370 | if (status != MMDB_SUCCESS) { 371 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 372 | "MMDB_open(\"%V\") failed - %s", &value[1], 373 | MMDB_strerror(status)); 374 | return NGX_CONF_ERROR; 375 | } 376 | 377 | save = *cf; 378 | cf->handler = ngx_stream_geoip2_parse_config; 379 | cf->handler_conf = (void *) database; 380 | 381 | rv = ngx_conf_parse(cf, NULL); 382 | *cf = save; 383 | return rv; 384 | } 385 | 386 | 387 | static char * 388 | ngx_stream_geoip2_parse_config(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) 389 | { 390 | ngx_stream_geoip2_db_t *database; 391 | ngx_str_t *value; 392 | time_t interval; 393 | 394 | value = cf->args->elts; 395 | 396 | if (value[0].data[0] == '$') { 397 | return ngx_stream_geoip2_add_variable(cf, dummy, conf); 398 | } 399 | 400 | if (value[0].len == 11 401 | && ngx_strncmp(value[0].data, "auto_reload", 11) == 0) { 402 | if ((int) cf->args->nelts != 2) { 403 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 404 | "invalid number of arguments for auto_reload"); 405 | return NGX_CONF_ERROR; 406 | } 407 | 408 | interval = ngx_parse_time(&value[1], true); 409 | 410 | if (interval == (time_t) NGX_ERROR) { 411 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 412 | "invalid interval for auto_reload \"%V\"", 413 | value[1]); 414 | return NGX_CONF_ERROR; 415 | } 416 | 417 | 418 | database = (ngx_stream_geoip2_db_t *) conf; 419 | database->check_interval = interval; 420 | return NGX_CONF_OK; 421 | } 422 | 423 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 424 | "invalid setting \"%V\"", &value[0]); 425 | return NGX_CONF_ERROR; 426 | } 427 | 428 | 429 | static char * 430 | ngx_stream_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) 431 | { 432 | ngx_stream_geoip2_db_t *database; 433 | ngx_str_t *value; 434 | int nelts; 435 | 436 | value = cf->args->elts; 437 | 438 | if (value[0].data[0] != '$') { 439 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 440 | "invalid variable name \"%V\"", &value[0]); 441 | return NGX_CONF_ERROR; 442 | } 443 | 444 | value[0].len--; 445 | value[0].data++; 446 | 447 | nelts = (int) cf->args->nelts; 448 | database = (ngx_stream_geoip2_db_t *) conf; 449 | 450 | if (nelts > 0 && value[1].len == 8 && ngx_strncmp(value[1].data, "metadata", 8) == 0) { 451 | return ngx_stream_geoip2_add_variable_metadata(cf, database); 452 | } 453 | 454 | return ngx_stream_geoip2_add_variable_geodata(cf, database); 455 | } 456 | 457 | 458 | static char * 459 | ngx_stream_geoip2_add_variable_metadata(ngx_conf_t *cf, ngx_stream_geoip2_db_t *database) 460 | { 461 | ngx_stream_geoip2_metadata_t *metadata; 462 | ngx_str_t *value, name; 463 | ngx_stream_variable_t *var; 464 | 465 | metadata = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip2_metadata_t)); 466 | if (metadata == NULL) { 467 | return NGX_CONF_ERROR; 468 | } 469 | 470 | value = cf->args->elts; 471 | name = value[0]; 472 | 473 | metadata->database = database; 474 | metadata->metavalue = value[2]; 475 | 476 | var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE); 477 | if (var == NULL) { 478 | return NGX_CONF_ERROR; 479 | } 480 | 481 | var->get_handler = ngx_stream_geoip2_metadata; 482 | var->data = (uintptr_t) metadata; 483 | 484 | return NGX_CONF_OK; 485 | } 486 | 487 | 488 | static char * 489 | ngx_stream_geoip2_add_variable_geodata(ngx_conf_t *cf, ngx_stream_geoip2_db_t *database) 490 | { 491 | ngx_stream_geoip2_ctx_t *geoip2; 492 | ngx_stream_compile_complex_value_t ccv; 493 | ngx_str_t *value, name, source; 494 | ngx_stream_variable_t *var; 495 | int i, nelts, idx; 496 | 497 | geoip2 = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip2_ctx_t)); 498 | if (geoip2 == NULL) { 499 | return NGX_CONF_ERROR; 500 | } 501 | 502 | geoip2->database = database; 503 | ngx_str_null(&source); 504 | 505 | value = cf->args->elts; 506 | name = value[0]; 507 | 508 | nelts = (int) cf->args->nelts; 509 | idx = 1; 510 | 511 | if (nelts > idx) { 512 | for (i = idx; i < nelts; i++) { 513 | if (ngx_strnstr(value[idx].data, "=", value[idx].len) == NULL) { 514 | break; 515 | } 516 | 517 | if (value[idx].len > 8 && ngx_strncmp(value[idx].data, "default=", 8) == 0) { 518 | if (geoip2->default_value.len > 0) { 519 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 520 | "default has already been declared for \"$%V\"", &name); 521 | return NGX_CONF_ERROR; 522 | } 523 | 524 | geoip2->default_value.len = value[idx].len - 8; 525 | geoip2->default_value.data = value[idx].data + 8; 526 | 527 | } else if (value[idx].len > 7 && ngx_strncmp(value[idx].data, "source=", 7) == 0) { 528 | if (source.len > 0) { 529 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 530 | "source has already been declared for \"$%V\"", &name); 531 | return NGX_CONF_ERROR; 532 | } 533 | 534 | source.len = value[idx].len - 7; 535 | source.data = value[idx].data + 7; 536 | 537 | if (source.data[0] != '$') { 538 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 539 | "invalid source variable name \"%V\"", &source); 540 | return NGX_CONF_ERROR; 541 | } 542 | 543 | ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); 544 | ccv.cf = cf; 545 | ccv.value = &source; 546 | ccv.complex_value = &geoip2->source; 547 | 548 | if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { 549 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 550 | "unable to compile \"%V\" for \"$%V\"", &source, &name); 551 | return NGX_CONF_ERROR; 552 | } 553 | 554 | } else { 555 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 556 | "invalid setting \"%V\" for \"$%V\"", &value[idx], &name); 557 | return NGX_CONF_ERROR; 558 | } 559 | 560 | idx++; 561 | } 562 | } 563 | 564 | var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE); 565 | if (var == NULL) { 566 | return NGX_CONF_ERROR; 567 | } 568 | 569 | geoip2->lookup = ngx_pcalloc(cf->pool, 570 | sizeof(const char *) * (cf->args->nelts - (idx - 1))); 571 | 572 | if (geoip2->lookup == NULL) { 573 | return NGX_CONF_ERROR; 574 | } 575 | 576 | for (i = idx; i < nelts; i++) { 577 | geoip2->lookup[i - idx] = (char *) value[i].data; 578 | } 579 | geoip2->lookup[i - idx] = NULL; 580 | 581 | var->get_handler = ngx_stream_geoip2_variable; 582 | var->data = (uintptr_t) geoip2; 583 | 584 | return NGX_CONF_OK; 585 | } 586 | 587 | 588 | static void 589 | ngx_stream_geoip2_cleanup(void *data) 590 | { 591 | ngx_queue_t *q; 592 | ngx_stream_geoip2_db_t *database; 593 | ngx_stream_geoip2_conf_t *gcf = data; 594 | 595 | while (!ngx_queue_empty(&gcf->databases)) { 596 | q = ngx_queue_head(&gcf->databases); 597 | ngx_queue_remove(q); 598 | database = ngx_queue_data(q, ngx_stream_geoip2_db_t, queue); 599 | MMDB_close(&database->mmdb); 600 | } 601 | } 602 | 603 | 604 | static ngx_int_t 605 | ngx_stream_geoip2_log_handler(ngx_stream_session_t *s) 606 | { 607 | int status; 608 | MMDB_s tmpdb; 609 | ngx_queue_t *q; 610 | ngx_file_info_t fi; 611 | ngx_stream_geoip2_db_t *database; 612 | ngx_stream_geoip2_conf_t *gcf; 613 | 614 | ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, 615 | "geoip2 stream log handler"); 616 | 617 | gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip2_module); 618 | 619 | if (ngx_queue_empty(&gcf->databases)) { 620 | return NGX_OK; 621 | } 622 | 623 | for (q = ngx_queue_head(&gcf->databases); 624 | q != ngx_queue_sentinel(&gcf->databases); 625 | q = ngx_queue_next(q)) 626 | { 627 | database = ngx_queue_data(q, ngx_stream_geoip2_db_t, queue); 628 | if (database->check_interval == 0) { 629 | continue; 630 | } 631 | 632 | if ((database->last_check + database->check_interval) 633 | > ngx_time()) 634 | { 635 | continue; 636 | } 637 | 638 | database->last_check = ngx_time(); 639 | 640 | if (ngx_file_info(database->mmdb.filename, &fi) == NGX_FILE_ERROR) { 641 | ngx_log_error(NGX_LOG_EMERG, s->connection->log, ngx_errno, 642 | ngx_file_info_n " \"%s\" failed", 643 | database->mmdb.filename); 644 | 645 | continue; 646 | } 647 | 648 | if (ngx_file_mtime(&fi) <= database->last_change) { 649 | continue; 650 | } 651 | 652 | /* do the reload */ 653 | 654 | ngx_memzero(&tmpdb, sizeof(MMDB_s)); 655 | status = MMDB_open(database->mmdb.filename, MMDB_MODE_MMAP, &tmpdb); 656 | 657 | if (status != MMDB_SUCCESS) { 658 | ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, 659 | "MMDB_open(\"%s\") failed to reload - %s", 660 | database->mmdb.filename, MMDB_strerror(status)); 661 | 662 | continue; 663 | } 664 | 665 | database->last_change = ngx_file_mtime(&fi); 666 | MMDB_close(&database->mmdb); 667 | database->mmdb = tmpdb; 668 | 669 | ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, 670 | "Reload MMDB \"%s\"", 671 | database->mmdb.filename); 672 | } 673 | 674 | return NGX_OK; 675 | } 676 | 677 | 678 | static ngx_int_t 679 | ngx_stream_geoip2_init(ngx_conf_t *cf) 680 | { 681 | ngx_stream_handler_pt *h; 682 | ngx_stream_core_main_conf_t *cmcf; 683 | 684 | cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); 685 | 686 | h = ngx_array_push(&cmcf->phases[NGX_STREAM_LOG_PHASE].handlers); 687 | if (h == NULL) { 688 | return NGX_ERROR; 689 | } 690 | 691 | *h = ngx_stream_geoip2_log_handler; 692 | 693 | return NGX_OK; 694 | } 695 | -------------------------------------------------------------------------------- /ngx_http_geoip2_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Lee Valentine 3 | * 4 | * Based on nginx's 'ngx_http_geoip_module.c' by Igor Sysoev 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | 15 | typedef struct { 16 | MMDB_s mmdb; 17 | MMDB_lookup_result_s result; 18 | time_t last_check; 19 | time_t last_change; 20 | time_t check_interval; 21 | #if (NGX_HAVE_INET6) 22 | uint8_t address[16]; 23 | #else 24 | unsigned long address; 25 | #endif 26 | ngx_queue_t queue; 27 | } ngx_http_geoip2_db_t; 28 | 29 | typedef struct { 30 | ngx_queue_t databases; 31 | ngx_array_t *proxies; 32 | ngx_flag_t proxy_recursive; 33 | } ngx_http_geoip2_conf_t; 34 | 35 | typedef struct { 36 | ngx_http_geoip2_db_t *database; 37 | const char **lookup; 38 | ngx_str_t default_value; 39 | ngx_http_complex_value_t source; 40 | } ngx_http_geoip2_ctx_t; 41 | 42 | typedef struct { 43 | ngx_http_geoip2_db_t *database; 44 | ngx_str_t metavalue; 45 | } ngx_http_geoip2_metadata_t; 46 | 47 | 48 | static ngx_int_t ngx_http_geoip2_variable(ngx_http_request_t *r, 49 | ngx_http_variable_value_t *v, uintptr_t data); 50 | static ngx_int_t ngx_http_geoip2_metadata(ngx_http_request_t *r, 51 | ngx_http_variable_value_t *v, uintptr_t data); 52 | static void *ngx_http_geoip2_create_conf(ngx_conf_t *cf); 53 | static char *ngx_http_geoip2_init_conf(ngx_conf_t *cf, void *conf); 54 | static char *ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, 55 | void *conf); 56 | static char *ngx_http_geoip2_parse_config(ngx_conf_t *cf, ngx_command_t *dummy, 57 | void *conf); 58 | static char *ngx_http_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, 59 | void *conf); 60 | static char *ngx_http_geoip2_add_variable_geodata(ngx_conf_t *cf, 61 | ngx_http_geoip2_db_t *database); 62 | static char *ngx_http_geoip2_add_variable_metadata(ngx_conf_t *cf, 63 | ngx_http_geoip2_db_t *database); 64 | static char *ngx_http_geoip2_proxy(ngx_conf_t *cf, ngx_command_t *cmd, 65 | void *conf); 66 | static ngx_int_t ngx_http_geoip2_cidr_value(ngx_conf_t *cf, ngx_str_t *net, 67 | ngx_cidr_t *cidr); 68 | static void ngx_http_geoip2_cleanup(void *data); 69 | static ngx_int_t ngx_http_geoip2_init(ngx_conf_t *cf); 70 | 71 | 72 | #define FORMAT(fmt, ...) do { \ 73 | p = ngx_palloc(r->pool, NGX_OFF_T_LEN); \ 74 | if (p == NULL) { \ 75 | return NGX_ERROR; \ 76 | } \ 77 | v->len = ngx_sprintf(p, fmt, __VA_ARGS__) - p; \ 78 | v->data = p; \ 79 | } while (0) 80 | 81 | static ngx_command_t ngx_http_geoip2_commands[] = { 82 | 83 | { ngx_string("geoip2"), 84 | NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, 85 | ngx_http_geoip2, 86 | NGX_HTTP_MAIN_CONF_OFFSET, 87 | 0, 88 | NULL }, 89 | 90 | { ngx_string("geoip2_proxy"), 91 | NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, 92 | ngx_http_geoip2_proxy, 93 | NGX_HTTP_MAIN_CONF_OFFSET, 94 | 0, 95 | NULL }, 96 | 97 | { ngx_string("geoip2_proxy_recursive"), 98 | NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG, 99 | ngx_conf_set_flag_slot, 100 | NGX_HTTP_MAIN_CONF_OFFSET, 101 | offsetof(ngx_http_geoip2_conf_t, proxy_recursive), 102 | NULL }, 103 | 104 | ngx_null_command 105 | }; 106 | 107 | 108 | static ngx_http_module_t ngx_http_geoip2_module_ctx = { 109 | NULL, /* preconfiguration */ 110 | ngx_http_geoip2_init, /* postconfiguration */ 111 | 112 | ngx_http_geoip2_create_conf, /* create main configuration */ 113 | ngx_http_geoip2_init_conf, /* init main configuration */ 114 | 115 | NULL, /* create server configuration */ 116 | NULL, /* merge server configuration */ 117 | 118 | NULL, /* create location configuration */ 119 | NULL /* merge location configuration */ 120 | }; 121 | 122 | 123 | ngx_module_t ngx_http_geoip2_module = { 124 | NGX_MODULE_V1, 125 | &ngx_http_geoip2_module_ctx, /* module context */ 126 | ngx_http_geoip2_commands, /* module directives */ 127 | NGX_HTTP_MODULE, /* module type */ 128 | NULL, /* init master */ 129 | NULL, /* init module */ 130 | NULL, /* init process */ 131 | NULL, /* init thread */ 132 | NULL, /* exit thread */ 133 | NULL, /* exit process */ 134 | NULL, /* exit master */ 135 | NGX_MODULE_V1_PADDING 136 | }; 137 | 138 | 139 | static ngx_int_t 140 | ngx_http_geoip2_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, 141 | uintptr_t data) 142 | { 143 | ngx_http_geoip2_ctx_t *geoip2 = (ngx_http_geoip2_ctx_t *) data; 144 | ngx_http_geoip2_db_t *database = geoip2->database; 145 | int mmdb_error; 146 | MMDB_entry_data_s entry_data; 147 | ngx_http_geoip2_conf_t *gcf; 148 | ngx_addr_t addr; 149 | #if defined(nginx_version) && nginx_version >= 1023000 150 | ngx_table_elt_t *xfwd; 151 | #else 152 | ngx_array_t *xfwd; 153 | #endif 154 | u_char *p; 155 | ngx_str_t val; 156 | 157 | #if (NGX_HAVE_INET6) 158 | uint8_t address[16], *addressp = address; 159 | #else 160 | unsigned long address; 161 | #endif 162 | 163 | if (geoip2->source.value.len > 0) { 164 | if (ngx_http_complex_value(r, &geoip2->source, &val) != NGX_OK) { 165 | goto not_found; 166 | } 167 | 168 | if (ngx_parse_addr(r->pool, &addr, val.data, val.len) != NGX_OK) { 169 | goto not_found; 170 | } 171 | } else { 172 | gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip2_module); 173 | addr.sockaddr = r->connection->sockaddr; 174 | addr.socklen = r->connection->socklen; 175 | 176 | #if defined(nginx_version) && nginx_version >= 1023000 177 | xfwd = r->headers_in.x_forwarded_for; 178 | 179 | if (xfwd != NULL && gcf->proxies != NULL) { 180 | #else 181 | xfwd = &r->headers_in.x_forwarded_for; 182 | 183 | if (xfwd->nelts > 0 && gcf->proxies != NULL) { 184 | #endif 185 | (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, 186 | gcf->proxies, gcf->proxy_recursive); 187 | } 188 | } 189 | 190 | switch (addr.sockaddr->sa_family) { 191 | case AF_INET: 192 | #if (NGX_HAVE_INET6) 193 | ngx_memset(addressp, 0, 12); 194 | ngx_memcpy(addressp + 12, &((struct sockaddr_in *) 195 | addr.sockaddr)->sin_addr.s_addr, 4); 196 | break; 197 | 198 | case AF_INET6: 199 | ngx_memcpy(addressp, &((struct sockaddr_in6 *) 200 | addr.sockaddr)->sin6_addr.s6_addr, 16); 201 | #else 202 | address = ((struct sockaddr_in *)addr.sockaddr)->sin_addr.s_addr; 203 | #endif 204 | break; 205 | 206 | default: 207 | goto not_found; 208 | } 209 | 210 | #if (NGX_HAVE_INET6) 211 | if (ngx_memcmp(&address, &database->address, sizeof(address)) 212 | != 0) { 213 | #else 214 | if (address != database->address) { 215 | #endif 216 | memcpy(&database->address, &address, sizeof(address)); 217 | database->result = MMDB_lookup_sockaddr(&database->mmdb, 218 | addr.sockaddr, &mmdb_error); 219 | 220 | if (mmdb_error != MMDB_SUCCESS) { 221 | goto not_found; 222 | } 223 | } 224 | 225 | if (!database->result.found_entry 226 | || MMDB_aget_value(&database->result.entry, &entry_data, 227 | geoip2->lookup) != MMDB_SUCCESS) { 228 | goto not_found; 229 | } 230 | 231 | if (!entry_data.has_data) { 232 | goto not_found; 233 | } 234 | 235 | switch (entry_data.type) { 236 | case MMDB_DATA_TYPE_BOOLEAN: 237 | FORMAT("%d", entry_data.boolean); 238 | break; 239 | case MMDB_DATA_TYPE_UTF8_STRING: 240 | v->len = entry_data.data_size; 241 | v->data = ngx_pnalloc(r->pool, v->len); 242 | if (v->data == NULL) { 243 | return NGX_ERROR; 244 | } 245 | ngx_memcpy(v->data, (u_char *) entry_data.utf8_string, v->len); 246 | break; 247 | case MMDB_DATA_TYPE_BYTES: 248 | v->len = entry_data.data_size; 249 | v->data = ngx_pnalloc(r->pool, v->len); 250 | if (v->data == NULL) { 251 | return NGX_ERROR; 252 | } 253 | ngx_memcpy(v->data, (u_char *) entry_data.bytes, v->len); 254 | break; 255 | case MMDB_DATA_TYPE_FLOAT: 256 | FORMAT("%.5f", entry_data.float_value); 257 | break; 258 | case MMDB_DATA_TYPE_DOUBLE: 259 | FORMAT("%.5f", entry_data.double_value); 260 | break; 261 | case MMDB_DATA_TYPE_UINT16: 262 | FORMAT("%uD", entry_data.uint16); 263 | break; 264 | case MMDB_DATA_TYPE_UINT32: 265 | FORMAT("%uD", entry_data.uint32); 266 | break; 267 | case MMDB_DATA_TYPE_INT32: 268 | FORMAT("%D", entry_data.int32); 269 | break; 270 | case MMDB_DATA_TYPE_UINT64: 271 | FORMAT("%uL", entry_data.uint64); 272 | break; 273 | case MMDB_DATA_TYPE_UINT128: ; 274 | #if MMDB_UINT128_IS_BYTE_ARRAY 275 | uint8_t *val = (uint8_t *)entry_data.uint128; 276 | FORMAT( "0x%02x%02x%02x%02x%02x%02x%02x%02x" 277 | "%02x%02x%02x%02x%02x%02x%02x%02x", 278 | val[0], val[1], val[2], val[3], 279 | val[4], val[5], val[6], val[7], 280 | val[8], val[9], val[10], val[11], 281 | val[12], val[13], val[14], val[15]); 282 | #else 283 | mmdb_uint128_t val = entry_data.uint128; 284 | FORMAT("0x%016uxL%016uxL", 285 | (uint64_t) (val >> 64), (uint64_t) val); 286 | #endif 287 | break; 288 | default: 289 | goto not_found; 290 | } 291 | 292 | v->valid = 1; 293 | v->no_cacheable = 0; 294 | v->not_found = 0; 295 | 296 | return NGX_OK; 297 | 298 | not_found: 299 | if (geoip2->default_value.len > 0) { 300 | v->data = geoip2->default_value.data; 301 | v->len = geoip2->default_value.len; 302 | 303 | v->valid = 1; 304 | v->no_cacheable = 0; 305 | v->not_found = 0; 306 | } else { 307 | v->not_found = 1; 308 | } 309 | 310 | return NGX_OK; 311 | } 312 | 313 | 314 | static ngx_int_t 315 | ngx_http_geoip2_metadata(ngx_http_request_t *r, ngx_http_variable_value_t *v, 316 | uintptr_t data) 317 | { 318 | ngx_http_geoip2_metadata_t *metadata = (ngx_http_geoip2_metadata_t *) data; 319 | ngx_http_geoip2_db_t *database = metadata->database; 320 | u_char *p; 321 | 322 | if (ngx_strncmp(metadata->metavalue.data, "build_epoch", 11) == 0) { 323 | FORMAT("%uL", database->mmdb.metadata.build_epoch); 324 | } else if (ngx_strncmp(metadata->metavalue.data, "last_check", 10) == 0) { 325 | FORMAT("%T", database->last_check); 326 | } else if (ngx_strncmp(metadata->metavalue.data, "last_change", 11) == 0) { 327 | FORMAT("%T", database->last_change); 328 | } else { 329 | v->not_found = 1; 330 | return NGX_OK; 331 | } 332 | 333 | v->valid = 1; 334 | v->no_cacheable = 0; 335 | v->not_found = 0; 336 | 337 | return NGX_OK; 338 | } 339 | 340 | 341 | static void * 342 | ngx_http_geoip2_create_conf(ngx_conf_t *cf) 343 | { 344 | ngx_pool_cleanup_t *cln; 345 | ngx_http_geoip2_conf_t *conf; 346 | 347 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip2_conf_t)); 348 | if (conf == NULL) { 349 | return NULL; 350 | } 351 | 352 | conf->proxy_recursive = NGX_CONF_UNSET; 353 | 354 | cln = ngx_pool_cleanup_add(cf->pool, 0); 355 | if (cln == NULL) { 356 | return NULL; 357 | } 358 | 359 | ngx_queue_init(&conf->databases); 360 | 361 | cln->handler = ngx_http_geoip2_cleanup; 362 | cln->data = conf; 363 | 364 | return conf; 365 | } 366 | 367 | 368 | static char * 369 | ngx_http_geoip2(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 370 | { 371 | ngx_http_geoip2_conf_t *gcf = conf; 372 | ngx_str_t *value; 373 | int status; 374 | ngx_http_geoip2_db_t *database; 375 | char *rv; 376 | ngx_conf_t save; 377 | ngx_queue_t *q; 378 | 379 | value = cf->args->elts; 380 | 381 | if (value[1].data && value[1].data[0] != '/') { 382 | if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) { 383 | return NGX_CONF_ERROR; 384 | } 385 | } 386 | 387 | if (!ngx_queue_empty(&gcf->databases)) { 388 | for (q = ngx_queue_head(&gcf->databases); 389 | q != ngx_queue_sentinel(&gcf->databases); 390 | q = ngx_queue_next(q)) 391 | { 392 | database = ngx_queue_data(q, ngx_http_geoip2_db_t, queue); 393 | if (ngx_strcmp(value[1].data, database->mmdb.filename) == 0) { 394 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 395 | "Duplicate GeoIP2 mmdb - %V", &value[1]); 396 | return NGX_CONF_ERROR; 397 | } 398 | } 399 | } 400 | 401 | database = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip2_db_t)); 402 | if (database == NULL) { 403 | return NGX_CONF_ERROR; 404 | } 405 | 406 | ngx_queue_insert_tail(&gcf->databases, &database->queue); 407 | database->last_check = database->last_change = ngx_time(); 408 | 409 | status = MMDB_open((char *) value[1].data, MMDB_MODE_MMAP, &database->mmdb); 410 | 411 | if (status != MMDB_SUCCESS) { 412 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 413 | "MMDB_open(\"%V\") failed - %s", &value[1], 414 | MMDB_strerror(status)); 415 | return NGX_CONF_ERROR; 416 | } 417 | 418 | save = *cf; 419 | cf->handler = ngx_http_geoip2_parse_config; 420 | cf->handler_conf = (void *) database; 421 | 422 | rv = ngx_conf_parse(cf, NULL); 423 | *cf = save; 424 | return rv; 425 | } 426 | 427 | 428 | static char * 429 | ngx_http_geoip2_parse_config(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) 430 | { 431 | ngx_http_geoip2_db_t *database; 432 | ngx_str_t *value; 433 | time_t interval; 434 | 435 | value = cf->args->elts; 436 | 437 | if (value[0].data[0] == '$') { 438 | return ngx_http_geoip2_add_variable(cf, dummy, conf); 439 | } 440 | 441 | if (value[0].len == 11 442 | && ngx_strncmp(value[0].data, "auto_reload", 11) == 0) { 443 | if ((int) cf->args->nelts != 2) { 444 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 445 | "invalid number of arguments for auto_reload"); 446 | return NGX_CONF_ERROR; 447 | } 448 | 449 | interval = ngx_parse_time(&value[1], true); 450 | 451 | if (interval == (time_t) NGX_ERROR) { 452 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 453 | "invalid interval for auto_reload \"%V\"", 454 | &value[1]); 455 | return NGX_CONF_ERROR; 456 | } 457 | 458 | 459 | database = (ngx_http_geoip2_db_t *) conf; 460 | database->check_interval = interval; 461 | return NGX_CONF_OK; 462 | } 463 | 464 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 465 | "invalid setting \"%V\"", &value[0]); 466 | return NGX_CONF_ERROR; 467 | } 468 | 469 | 470 | static char * 471 | ngx_http_geoip2_add_variable(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) 472 | { 473 | ngx_http_geoip2_db_t *database; 474 | ngx_str_t *value; 475 | int nelts; 476 | 477 | value = cf->args->elts; 478 | 479 | if (value[0].data[0] != '$') { 480 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 481 | "invalid variable name \"%V\"", &value[0]); 482 | return NGX_CONF_ERROR; 483 | } 484 | 485 | value[0].len--; 486 | value[0].data++; 487 | 488 | nelts = (int) cf->args->nelts; 489 | database = (ngx_http_geoip2_db_t *) conf; 490 | 491 | if (nelts > 0 && value[1].len == 8 && ngx_strncmp(value[1].data, "metadata", 8) == 0) { 492 | return ngx_http_geoip2_add_variable_metadata(cf, database); 493 | } 494 | 495 | return ngx_http_geoip2_add_variable_geodata(cf, database); 496 | } 497 | 498 | 499 | static char * 500 | ngx_http_geoip2_add_variable_metadata(ngx_conf_t *cf, ngx_http_geoip2_db_t *database) 501 | { 502 | ngx_http_geoip2_metadata_t *metadata; 503 | ngx_str_t *value, name; 504 | ngx_http_variable_t *var; 505 | 506 | metadata = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip2_metadata_t)); 507 | if (metadata == NULL) { 508 | return NGX_CONF_ERROR; 509 | } 510 | 511 | value = cf->args->elts; 512 | name = value[0]; 513 | 514 | metadata->database = database; 515 | metadata->metavalue = value[2]; 516 | 517 | var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); 518 | if (var == NULL) { 519 | return NGX_CONF_ERROR; 520 | } 521 | 522 | var->get_handler = ngx_http_geoip2_metadata; 523 | var->data = (uintptr_t) metadata; 524 | 525 | return NGX_CONF_OK; 526 | } 527 | 528 | 529 | static char * 530 | ngx_http_geoip2_add_variable_geodata(ngx_conf_t *cf, ngx_http_geoip2_db_t *database) 531 | { 532 | ngx_http_geoip2_ctx_t *geoip2; 533 | ngx_http_compile_complex_value_t ccv; 534 | ngx_str_t *value, name, source; 535 | ngx_http_variable_t *var; 536 | int i, nelts, idx; 537 | 538 | geoip2 = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip2_ctx_t)); 539 | if (geoip2 == NULL) { 540 | return NGX_CONF_ERROR; 541 | } 542 | 543 | geoip2->database = database; 544 | ngx_str_null(&source); 545 | 546 | value = cf->args->elts; 547 | name = value[0]; 548 | 549 | nelts = (int) cf->args->nelts; 550 | idx = 1; 551 | 552 | if (nelts > idx) { 553 | for (i = idx; i < nelts; i++) { 554 | if (ngx_strnstr(value[idx].data, "=", value[idx].len) == NULL) { 555 | break; 556 | } 557 | 558 | if (value[idx].len > 8 && ngx_strncmp(value[idx].data, "default=", 8) == 0) { 559 | if (geoip2->default_value.len > 0) { 560 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 561 | "default has already been declared for \"$%V\"", &name); 562 | return NGX_CONF_ERROR; 563 | } 564 | 565 | geoip2->default_value.len = value[idx].len - 8; 566 | geoip2->default_value.data = value[idx].data + 8; 567 | } else if (value[idx].len > 7 && ngx_strncmp(value[idx].data, "source=", 7) == 0) { 568 | if (source.len > 0) { 569 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 570 | "source has already been declared for \"$%V\"", &name); 571 | return NGX_CONF_ERROR; 572 | } 573 | 574 | source.len = value[idx].len - 7; 575 | source.data = value[idx].data + 7; 576 | 577 | if (source.data[0] != '$') { 578 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 579 | "invalid source variable name \"%V\"", &source); 580 | return NGX_CONF_ERROR; 581 | } 582 | 583 | ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); 584 | ccv.cf = cf; 585 | ccv.value = &source; 586 | ccv.complex_value = &geoip2->source; 587 | 588 | if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { 589 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 590 | "unable to compile \"%V\" for \"$%V\"", &source, &name); 591 | return NGX_CONF_ERROR; 592 | } 593 | } else { 594 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 595 | "invalid setting \"%V\" for \"$%V\"", &value[idx], &name); 596 | return NGX_CONF_ERROR; 597 | } 598 | 599 | idx++; 600 | } 601 | } 602 | 603 | var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); 604 | if (var == NULL) { 605 | return NGX_CONF_ERROR; 606 | } 607 | 608 | geoip2->lookup = ngx_pcalloc(cf->pool, sizeof(const char *) * 609 | (cf->args->nelts - (idx - 1))); 610 | 611 | if (geoip2->lookup == NULL) { 612 | return NGX_CONF_ERROR; 613 | } 614 | 615 | for (i = idx; i < nelts; i++) { 616 | geoip2->lookup[i - idx] = (char *) value[i].data; 617 | } 618 | geoip2->lookup[i - idx] = NULL; 619 | 620 | var->get_handler = ngx_http_geoip2_variable; 621 | var->data = (uintptr_t) geoip2; 622 | 623 | return NGX_CONF_OK; 624 | } 625 | 626 | 627 | static char * 628 | ngx_http_geoip2_init_conf(ngx_conf_t *cf, void *conf) 629 | { 630 | ngx_http_geoip2_conf_t *gcf = conf; 631 | ngx_conf_init_value(gcf->proxy_recursive, 0); 632 | return NGX_CONF_OK; 633 | } 634 | 635 | 636 | static char * 637 | ngx_http_geoip2_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 638 | { 639 | ngx_http_geoip2_conf_t *gcf = conf; 640 | ngx_str_t *value; 641 | ngx_cidr_t cidr, *c; 642 | 643 | value = cf->args->elts; 644 | 645 | if (ngx_http_geoip2_cidr_value(cf, &value[1], &cidr) != NGX_OK) { 646 | return NGX_CONF_ERROR; 647 | } 648 | 649 | if (gcf->proxies == NULL) { 650 | gcf->proxies = ngx_array_create(cf->pool, 4, sizeof(ngx_cidr_t)); 651 | if (gcf->proxies == NULL) { 652 | return NGX_CONF_ERROR; 653 | } 654 | } 655 | 656 | c = ngx_array_push(gcf->proxies); 657 | if (c == NULL) { 658 | return NGX_CONF_ERROR; 659 | } 660 | 661 | *c = cidr; 662 | 663 | return NGX_CONF_OK; 664 | } 665 | 666 | 667 | static ngx_int_t 668 | ngx_http_geoip2_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr) 669 | { 670 | ngx_int_t rc; 671 | 672 | if (ngx_strcmp(net->data, "255.255.255.255") == 0) { 673 | cidr->family = AF_INET; 674 | cidr->u.in.addr = 0xffffffff; 675 | cidr->u.in.mask = 0xffffffff; 676 | 677 | return NGX_OK; 678 | } 679 | 680 | rc = ngx_ptocidr(net, cidr); 681 | 682 | if (rc == NGX_ERROR) { 683 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 684 | "invalid network \"%V\"", net); 685 | return NGX_ERROR; 686 | } 687 | 688 | if (rc == NGX_DONE) { 689 | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, 690 | "low address bits of %V are meaningless", net); 691 | } 692 | 693 | return NGX_OK; 694 | } 695 | 696 | 697 | static void 698 | ngx_http_geoip2_cleanup(void *data) 699 | { 700 | ngx_http_geoip2_conf_t *gcf = data; 701 | ngx_queue_t *q; 702 | ngx_http_geoip2_db_t *database; 703 | 704 | while (!ngx_queue_empty(&gcf->databases)) { 705 | q = ngx_queue_head(&gcf->databases); 706 | ngx_queue_remove(q); 707 | database = ngx_queue_data(q, ngx_http_geoip2_db_t, queue); 708 | MMDB_close(&database->mmdb); 709 | } 710 | } 711 | 712 | 713 | static ngx_int_t 714 | ngx_http_geoip2_log_handler(ngx_http_request_t *r) 715 | { 716 | int status; 717 | MMDB_s tmpdb; 718 | ngx_queue_t *q; 719 | ngx_file_info_t fi; 720 | ngx_http_geoip2_db_t *database; 721 | ngx_http_geoip2_conf_t *gcf; 722 | 723 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 724 | "geoip2 http log handler"); 725 | 726 | gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip2_module); 727 | 728 | if (ngx_queue_empty(&gcf->databases)) { 729 | return NGX_OK; 730 | } 731 | 732 | for (q = ngx_queue_head(&gcf->databases); 733 | q != ngx_queue_sentinel(&gcf->databases); 734 | q = ngx_queue_next(q)) 735 | { 736 | database = ngx_queue_data(q, ngx_http_geoip2_db_t, queue); 737 | if (database->check_interval == 0) { 738 | continue; 739 | } 740 | 741 | if ((database->last_check + database->check_interval) 742 | > ngx_time()) 743 | { 744 | continue; 745 | } 746 | 747 | database->last_check = ngx_time(); 748 | 749 | if (ngx_file_info(database->mmdb.filename, &fi) == NGX_FILE_ERROR) { 750 | ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, 751 | ngx_file_info_n " \"%s\" failed", 752 | database->mmdb.filename); 753 | 754 | continue; 755 | } 756 | 757 | if (ngx_file_mtime(&fi) <= database->last_change) { 758 | continue; 759 | } 760 | 761 | /* do the reload */ 762 | 763 | ngx_memzero(&tmpdb, sizeof(MMDB_s)); 764 | status = MMDB_open(database->mmdb.filename, MMDB_MODE_MMAP, &tmpdb); 765 | 766 | if (status != MMDB_SUCCESS) { 767 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 768 | "MMDB_open(\"%s\") failed to reload - %s", 769 | database->mmdb.filename, MMDB_strerror(status)); 770 | 771 | continue; 772 | } 773 | 774 | database->last_change = ngx_file_mtime(&fi); 775 | MMDB_close(&database->mmdb); 776 | database->mmdb = tmpdb; 777 | 778 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, 779 | "Reload MMDB \"%s\"", 780 | database->mmdb.filename); 781 | } 782 | 783 | return NGX_OK; 784 | } 785 | 786 | 787 | static ngx_int_t 788 | ngx_http_geoip2_init(ngx_conf_t *cf) 789 | { 790 | ngx_http_handler_pt *h; 791 | ngx_http_core_main_conf_t *cmcf; 792 | 793 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 794 | 795 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers); 796 | if (h == NULL) { 797 | return NGX_ERROR; 798 | } 799 | 800 | *h = ngx_http_geoip2_log_handler; 801 | 802 | return NGX_OK; 803 | } 804 | --------------------------------------------------------------------------------