|  |  | 
|  | /* | 
|  | * Copyright (C) Igor Sysoev | 
|  | * Copyright (C) Nginx, Inc. | 
|  | */ | 
|  |  | 
|  |  | 
|  | #include <ngx_config.h> | 
|  | #include <ngx_core.h> | 
|  | #include <ngx_http.h> | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | ngx_http_variable_value_t       *value; | 
|  | u_short                          start; | 
|  | u_short                          end; | 
|  | } ngx_http_geo_range_t; | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | ngx_radix_tree_t                *tree; | 
|  | #if (NGX_HAVE_INET6) | 
|  | ngx_radix_tree_t                *tree6; | 
|  | #endif | 
|  | } ngx_http_geo_trees_t; | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | ngx_http_geo_range_t           **low; | 
|  | ngx_http_variable_value_t       *default_value; | 
|  | } ngx_http_geo_high_ranges_t; | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | ngx_str_node_t                   sn; | 
|  | ngx_http_variable_value_t       *value; | 
|  | size_t                           offset; | 
|  | } ngx_http_geo_variable_value_node_t; | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | ngx_http_variable_value_t       *value; | 
|  | ngx_str_t                       *net; | 
|  | ngx_http_geo_high_ranges_t       high; | 
|  | ngx_radix_tree_t                *tree; | 
|  | #if (NGX_HAVE_INET6) | 
|  | ngx_radix_tree_t                *tree6; | 
|  | #endif | 
|  | ngx_rbtree_t                     rbtree; | 
|  | ngx_rbtree_node_t                sentinel; | 
|  | ngx_array_t                     *proxies; | 
|  | ngx_pool_t                      *pool; | 
|  | ngx_pool_t                      *temp_pool; | 
|  |  | 
|  | size_t                           data_size; | 
|  |  | 
|  | ngx_str_t                        include_name; | 
|  | ngx_uint_t                       includes; | 
|  | ngx_uint_t                       entries; | 
|  |  | 
|  | unsigned                         ranges:1; | 
|  | unsigned                         outside_entries:1; | 
|  | unsigned                         allow_binary_include:1; | 
|  | unsigned                         binary_include:1; | 
|  | unsigned                         proxy_recursive:1; | 
|  | } ngx_http_geo_conf_ctx_t; | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | union { | 
|  | ngx_http_geo_trees_t         trees; | 
|  | ngx_http_geo_high_ranges_t   high; | 
|  | } u; | 
|  |  | 
|  | ngx_array_t                     *proxies; | 
|  | unsigned                         proxy_recursive:1; | 
|  |  | 
|  | ngx_int_t                        index; | 
|  | } ngx_http_geo_ctx_t; | 
|  |  | 
|  |  | 
|  | static ngx_int_t ngx_http_geo_addr(ngx_http_request_t *r, | 
|  | ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr); | 
|  | static ngx_int_t ngx_http_geo_real_addr(ngx_http_request_t *r, | 
|  | ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr); | 
|  | static char *ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); | 
|  | static char *ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf); | 
|  | static char *ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_str_t *value); | 
|  | static char *ngx_http_geo_add_range(ngx_conf_t *cf, | 
|  | ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end); | 
|  | static ngx_uint_t ngx_http_geo_delete_range(ngx_conf_t *cf, | 
|  | ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end); | 
|  | static char *ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_str_t *value); | 
|  | static char *ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net); | 
|  | static ngx_http_variable_value_t *ngx_http_geo_value(ngx_conf_t *cf, | 
|  | ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value); | 
|  | static char *ngx_http_geo_add_proxy(ngx_conf_t *cf, | 
|  | ngx_http_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr); | 
|  | static ngx_int_t ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, | 
|  | ngx_cidr_t *cidr); | 
|  | static char *ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_str_t *name); | 
|  | static ngx_int_t ngx_http_geo_include_binary_base(ngx_conf_t *cf, | 
|  | ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *name); | 
|  | static void ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx); | 
|  | static u_char *ngx_http_geo_copy_values(u_char *base, u_char *p, | 
|  | ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); | 
|  |  | 
|  |  | 
|  | static ngx_command_t  ngx_http_geo_commands[] = { | 
|  |  | 
|  | { ngx_string("geo"), | 
|  | NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12, | 
|  | ngx_http_geo_block, | 
|  | NGX_HTTP_MAIN_CONF_OFFSET, | 
|  | 0, | 
|  | NULL }, | 
|  |  | 
|  | ngx_null_command | 
|  | }; | 
|  |  | 
|  |  | 
|  | static ngx_http_module_t  ngx_http_geo_module_ctx = { | 
|  | NULL,                                  /* preconfiguration */ | 
|  | NULL,                                  /* postconfiguration */ | 
|  |  | 
|  | NULL,                                  /* create main configuration */ | 
|  | NULL,                                  /* init main configuration */ | 
|  |  | 
|  | NULL,                                  /* create server configuration */ | 
|  | NULL,                                  /* merge server configuration */ | 
|  |  | 
|  | NULL,                                  /* create location configuration */ | 
|  | NULL                                   /* merge location configuration */ | 
|  | }; | 
|  |  | 
|  |  | 
|  | ngx_module_t  ngx_http_geo_module = { | 
|  | NGX_MODULE_V1, | 
|  | &ngx_http_geo_module_ctx,              /* module context */ | 
|  | ngx_http_geo_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 | 
|  | }; | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | u_char    GEORNG[6]; | 
|  | u_char    version; | 
|  | u_char    ptr_size; | 
|  | uint32_t  endianness; | 
|  | uint32_t  crc32; | 
|  | } ngx_http_geo_header_t; | 
|  |  | 
|  |  | 
|  | static ngx_http_geo_header_t  ngx_http_geo_header = { | 
|  | { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0 | 
|  | }; | 
|  |  | 
|  |  | 
|  | /* geo range is AF_INET only */ | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_geo_cidr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, | 
|  | uintptr_t data) | 
|  | { | 
|  | ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data; | 
|  |  | 
|  | in_addr_t                   inaddr; | 
|  | ngx_addr_t                  addr; | 
|  | struct sockaddr_in         *sin; | 
|  | ngx_http_variable_value_t  *vv; | 
|  | #if (NGX_HAVE_INET6) | 
|  | u_char                     *p; | 
|  | struct in6_addr            *inaddr6; | 
|  | #endif | 
|  |  | 
|  | if (ngx_http_geo_addr(r, ctx, &addr) != NGX_OK) { | 
|  | vv = (ngx_http_variable_value_t *) | 
|  | ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE); | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | switch (addr.sockaddr->sa_family) { | 
|  |  | 
|  | #if (NGX_HAVE_INET6) | 
|  | case AF_INET6: | 
|  | inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; | 
|  | p = inaddr6->s6_addr; | 
|  |  | 
|  | if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { | 
|  | inaddr = p[12] << 24; | 
|  | inaddr += p[13] << 16; | 
|  | inaddr += p[14] << 8; | 
|  | inaddr += p[15]; | 
|  |  | 
|  | vv = (ngx_http_variable_value_t *) | 
|  | ngx_radix32tree_find(ctx->u.trees.tree, inaddr); | 
|  |  | 
|  | } else { | 
|  | vv = (ngx_http_variable_value_t *) | 
|  | ngx_radix128tree_find(ctx->u.trees.tree6, p); | 
|  | } | 
|  |  | 
|  | break; | 
|  | #endif | 
|  |  | 
|  | #if (NGX_HAVE_UNIX_DOMAIN) | 
|  | case AF_UNIX: | 
|  | vv = (ngx_http_variable_value_t *) | 
|  | ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE); | 
|  | break; | 
|  | #endif | 
|  |  | 
|  | default: /* AF_INET */ | 
|  | sin = (struct sockaddr_in *) addr.sockaddr; | 
|  | inaddr = ntohl(sin->sin_addr.s_addr); | 
|  |  | 
|  | vv = (ngx_http_variable_value_t *) | 
|  | ngx_radix32tree_find(ctx->u.trees.tree, inaddr); | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | done: | 
|  |  | 
|  | *v = *vv; | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http geo: %v", v); | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, | 
|  | uintptr_t data) | 
|  | { | 
|  | ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data; | 
|  |  | 
|  | in_addr_t              inaddr; | 
|  | ngx_addr_t             addr; | 
|  | ngx_uint_t             n; | 
|  | struct sockaddr_in    *sin; | 
|  | ngx_http_geo_range_t  *range; | 
|  | #if (NGX_HAVE_INET6) | 
|  | u_char                *p; | 
|  | struct in6_addr       *inaddr6; | 
|  | #endif | 
|  |  | 
|  | *v = *ctx->u.high.default_value; | 
|  |  | 
|  | if (ngx_http_geo_addr(r, ctx, &addr) == NGX_OK) { | 
|  |  | 
|  | switch (addr.sockaddr->sa_family) { | 
|  |  | 
|  | #if (NGX_HAVE_INET6) | 
|  | case AF_INET6: | 
|  | inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; | 
|  |  | 
|  | if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { | 
|  | p = inaddr6->s6_addr; | 
|  |  | 
|  | inaddr = p[12] << 24; | 
|  | inaddr += p[13] << 16; | 
|  | inaddr += p[14] << 8; | 
|  | inaddr += p[15]; | 
|  |  | 
|  | } else { | 
|  | inaddr = INADDR_NONE; | 
|  | } | 
|  |  | 
|  | break; | 
|  | #endif | 
|  |  | 
|  | #if (NGX_HAVE_UNIX_DOMAIN) | 
|  | case AF_UNIX: | 
|  | inaddr = INADDR_NONE; | 
|  | break; | 
|  | #endif | 
|  |  | 
|  | default: /* AF_INET */ | 
|  | sin = (struct sockaddr_in *) addr.sockaddr; | 
|  | inaddr = ntohl(sin->sin_addr.s_addr); | 
|  | break; | 
|  | } | 
|  |  | 
|  | } else { | 
|  | inaddr = INADDR_NONE; | 
|  | } | 
|  |  | 
|  | if (ctx->u.high.low) { | 
|  | range = ctx->u.high.low[inaddr >> 16]; | 
|  |  | 
|  | if (range) { | 
|  | n = inaddr & 0xffff; | 
|  | do { | 
|  | if (n >= (ngx_uint_t) range->start | 
|  | && n <= (ngx_uint_t) range->end) | 
|  | { | 
|  | *v = *range->value; | 
|  | break; | 
|  | } | 
|  | } while ((++range)->value); | 
|  | } | 
|  | } | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http geo: %v", v); | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx, | 
|  | ngx_addr_t *addr) | 
|  | { | 
|  | ngx_array_t  *xfwd; | 
|  |  | 
|  | if (ngx_http_geo_real_addr(r, ctx, addr) != NGX_OK) { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | xfwd = &r->headers_in.x_forwarded_for; | 
|  |  | 
|  | if (xfwd->nelts > 0 && ctx->proxies != NULL) { | 
|  | (void) ngx_http_get_forwarded_addr(r, addr, xfwd, NULL, | 
|  | ctx->proxies, ctx->proxy_recursive); | 
|  | } | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_geo_real_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx, | 
|  | ngx_addr_t *addr) | 
|  | { | 
|  | ngx_http_variable_value_t  *v; | 
|  |  | 
|  | if (ctx->index == -1) { | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http geo started: %V", &r->connection->addr_text); | 
|  |  | 
|  | addr->sockaddr = r->connection->sockaddr; | 
|  | addr->socklen = r->connection->socklen; | 
|  | /* addr->name = r->connection->addr_text; */ | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | v = ngx_http_get_flushed_variable(r, ctx->index); | 
|  |  | 
|  | if (v == NULL || v->not_found) { | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http geo not found"); | 
|  |  | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http geo started: %v", v); | 
|  |  | 
|  | if (ngx_parse_addr(r->pool, addr, v->data, v->len) == NGX_OK) { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  |  | 
|  | static char * | 
|  | ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | 
|  | { | 
|  | char                     *rv; | 
|  | size_t                    len; | 
|  | ngx_str_t                *value, name; | 
|  | ngx_uint_t                i; | 
|  | ngx_conf_t                save; | 
|  | ngx_pool_t               *pool; | 
|  | ngx_array_t              *a; | 
|  | ngx_http_variable_t      *var; | 
|  | ngx_http_geo_ctx_t       *geo; | 
|  | ngx_http_geo_conf_ctx_t   ctx; | 
|  | #if (NGX_HAVE_INET6) | 
|  | static struct in6_addr    zero; | 
|  | #endif | 
|  |  | 
|  | value = cf->args->elts; | 
|  |  | 
|  | geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t)); | 
|  | if (geo == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | name = value[1]; | 
|  |  | 
|  | if (name.data[0] != '$') { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "invalid variable name \"%V\"", &name); | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | name.len--; | 
|  | name.data++; | 
|  |  | 
|  | if (cf->args->nelts == 3) { | 
|  |  | 
|  | geo->index = ngx_http_get_variable_index(cf, &name); | 
|  | if (geo->index == NGX_ERROR) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | name = value[2]; | 
|  |  | 
|  | if (name.data[0] != '$') { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "invalid variable name \"%V\"", &name); | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | name.len--; | 
|  | name.data++; | 
|  |  | 
|  | } else { | 
|  | geo->index = -1; | 
|  | } | 
|  |  | 
|  | var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); | 
|  | if (var == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); | 
|  | if (pool == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | ngx_memzero(&ctx, sizeof(ngx_http_geo_conf_ctx_t)); | 
|  |  | 
|  | ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); | 
|  | if (ctx.temp_pool == NULL) { | 
|  | ngx_destroy_pool(pool); | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value); | 
|  |  | 
|  | ctx.pool = cf->pool; | 
|  | ctx.data_size = sizeof(ngx_http_geo_header_t) | 
|  | + sizeof(ngx_http_variable_value_t) | 
|  | + 0x10000 * sizeof(ngx_http_geo_range_t *); | 
|  | ctx.allow_binary_include = 1; | 
|  |  | 
|  | save = *cf; | 
|  | cf->pool = pool; | 
|  | cf->ctx = &ctx; | 
|  | cf->handler = ngx_http_geo; | 
|  | cf->handler_conf = conf; | 
|  |  | 
|  | rv = ngx_conf_parse(cf, NULL); | 
|  |  | 
|  | *cf = save; | 
|  |  | 
|  | if (rv != NGX_CONF_OK) { | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | geo->proxies = ctx.proxies; | 
|  | geo->proxy_recursive = ctx.proxy_recursive; | 
|  |  | 
|  | if (ctx.ranges) { | 
|  |  | 
|  | if (ctx.high.low && !ctx.binary_include) { | 
|  | for (i = 0; i < 0x10000; i++) { | 
|  | a = (ngx_array_t *) ctx.high.low[i]; | 
|  |  | 
|  | if (a == NULL) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (a->nelts == 0) { | 
|  | ctx.high.low[i] = NULL; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | len = a->nelts * sizeof(ngx_http_geo_range_t); | 
|  |  | 
|  | ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); | 
|  | if (ctx.high.low[i] == NULL) { | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | ngx_memcpy(ctx.high.low[i], a->elts, len); | 
|  | ctx.high.low[i][a->nelts].value = NULL; | 
|  | ctx.data_size += len + sizeof(void *); | 
|  | } | 
|  |  | 
|  | if (ctx.allow_binary_include | 
|  | && !ctx.outside_entries | 
|  | && ctx.entries > 100000 | 
|  | && ctx.includes == 1) | 
|  | { | 
|  | ngx_http_geo_create_binary_base(&ctx); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (ctx.high.default_value == NULL) { | 
|  | ctx.high.default_value = &ngx_http_variable_null_value; | 
|  | } | 
|  |  | 
|  | geo->u.high = ctx.high; | 
|  |  | 
|  | var->get_handler = ngx_http_geo_range_variable; | 
|  | var->data = (uintptr_t) geo; | 
|  |  | 
|  | } else { | 
|  | if (ctx.tree == NULL) { | 
|  | ctx.tree = ngx_radix_tree_create(cf->pool, -1); | 
|  | if (ctx.tree == NULL) { | 
|  | goto failed; | 
|  | } | 
|  | } | 
|  |  | 
|  | geo->u.trees.tree = ctx.tree; | 
|  |  | 
|  | #if (NGX_HAVE_INET6) | 
|  | if (ctx.tree6 == NULL) { | 
|  | ctx.tree6 = ngx_radix_tree_create(cf->pool, -1); | 
|  | if (ctx.tree6 == NULL) { | 
|  | goto failed; | 
|  | } | 
|  | } | 
|  |  | 
|  | geo->u.trees.tree6 = ctx.tree6; | 
|  | #endif | 
|  |  | 
|  | var->get_handler = ngx_http_geo_cidr_variable; | 
|  | var->data = (uintptr_t) geo; | 
|  |  | 
|  | if (ngx_radix32tree_insert(ctx.tree, 0, 0, | 
|  | (uintptr_t) &ngx_http_variable_null_value) | 
|  | == NGX_ERROR) | 
|  | { | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | /* NGX_BUSY is okay (default was set explicitly) */ | 
|  |  | 
|  | #if (NGX_HAVE_INET6) | 
|  | if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr, | 
|  | (uintptr_t) &ngx_http_variable_null_value) | 
|  | == NGX_ERROR) | 
|  | { | 
|  | goto failed; | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | ngx_destroy_pool(ctx.temp_pool); | 
|  | ngx_destroy_pool(pool); | 
|  |  | 
|  | return NGX_CONF_OK; | 
|  |  | 
|  | failed: | 
|  |  | 
|  | ngx_destroy_pool(ctx.temp_pool); | 
|  | ngx_destroy_pool(pool); | 
|  |  | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  |  | 
|  | static char * | 
|  | ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) | 
|  | { | 
|  | char                     *rv; | 
|  | ngx_str_t                *value; | 
|  | ngx_cidr_t                cidr; | 
|  | ngx_http_geo_conf_ctx_t  *ctx; | 
|  |  | 
|  | ctx = cf->ctx; | 
|  |  | 
|  | value = cf->args->elts; | 
|  |  | 
|  | if (cf->args->nelts == 1) { | 
|  |  | 
|  | if (ngx_strcmp(value[0].data, "ranges") == 0) { | 
|  |  | 
|  | if (ctx->tree | 
|  | #if (NGX_HAVE_INET6) | 
|  | || ctx->tree6 | 
|  | #endif | 
|  | ) | 
|  | { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "the \"ranges\" directive must be " | 
|  | "the first directive inside \"geo\" block"); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | ctx->ranges = 1; | 
|  |  | 
|  | rv = NGX_CONF_OK; | 
|  |  | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | else if (ngx_strcmp(value[0].data, "proxy_recursive") == 0) { | 
|  | ctx->proxy_recursive = 1; | 
|  | rv = NGX_CONF_OK; | 
|  | goto done; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (cf->args->nelts != 2) { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "invalid number of the geo parameters"); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | if (ngx_strcmp(value[0].data, "include") == 0) { | 
|  |  | 
|  | rv = ngx_http_geo_include(cf, ctx, &value[1]); | 
|  |  | 
|  | goto done; | 
|  |  | 
|  | } else if (ngx_strcmp(value[0].data, "proxy") == 0) { | 
|  |  | 
|  | if (ngx_http_geo_cidr_value(cf, &value[1], &cidr) != NGX_OK) { | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | rv = ngx_http_geo_add_proxy(cf, ctx, &cidr); | 
|  |  | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | if (ctx->ranges) { | 
|  | rv = ngx_http_geo_range(cf, ctx, value); | 
|  |  | 
|  | } else { | 
|  | rv = ngx_http_geo_cidr(cf, ctx, value); | 
|  | } | 
|  |  | 
|  | done: | 
|  |  | 
|  | ngx_reset_pool(cf->pool); | 
|  |  | 
|  | return rv; | 
|  |  | 
|  | failed: | 
|  |  | 
|  | ngx_reset_pool(cf->pool); | 
|  |  | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  |  | 
|  | static char * | 
|  | ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_str_t *value) | 
|  | { | 
|  | u_char      *p, *last; | 
|  | in_addr_t    start, end; | 
|  | ngx_str_t   *net; | 
|  | ngx_uint_t   del; | 
|  |  | 
|  | if (ngx_strcmp(value[0].data, "default") == 0) { | 
|  |  | 
|  | if (ctx->high.default_value) { | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "duplicate default geo range value: \"%V\", old value: \"%v\"", | 
|  | &value[1], ctx->high.default_value); | 
|  | } | 
|  |  | 
|  | ctx->high.default_value = ngx_http_geo_value(cf, ctx, &value[1]); | 
|  | if (ctx->high.default_value == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  | if (ctx->binary_include) { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "binary geo range base \"%s\" cannot be mixed with usual entries", | 
|  | ctx->include_name.data); | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | if (ctx->high.low == NULL) { | 
|  | ctx->high.low = ngx_pcalloc(ctx->pool, | 
|  | 0x10000 * sizeof(ngx_http_geo_range_t *)); | 
|  | if (ctx->high.low == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | ctx->entries++; | 
|  | ctx->outside_entries = 1; | 
|  |  | 
|  | if (ngx_strcmp(value[0].data, "delete") == 0) { | 
|  | net = &value[1]; | 
|  | del = 1; | 
|  |  | 
|  | } else { | 
|  | net = &value[0]; | 
|  | del = 0; | 
|  | } | 
|  |  | 
|  | last = net->data + net->len; | 
|  |  | 
|  | p = ngx_strlchr(net->data, last, '-'); | 
|  |  | 
|  | if (p == NULL) { | 
|  | goto invalid; | 
|  | } | 
|  |  | 
|  | start = ngx_inet_addr(net->data, p - net->data); | 
|  |  | 
|  | if (start == INADDR_NONE) { | 
|  | goto invalid; | 
|  | } | 
|  |  | 
|  | start = ntohl(start); | 
|  |  | 
|  | p++; | 
|  |  | 
|  | end = ngx_inet_addr(p, last - p); | 
|  |  | 
|  | if (end == INADDR_NONE) { | 
|  | goto invalid; | 
|  | } | 
|  |  | 
|  | end = ntohl(end); | 
|  |  | 
|  | if (start > end) { | 
|  | goto invalid; | 
|  | } | 
|  |  | 
|  | if (del) { | 
|  | if (ngx_http_geo_delete_range(cf, ctx, start, end)) { | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "no address range \"%V\" to delete", net); | 
|  | } | 
|  |  | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  | ctx->value = ngx_http_geo_value(cf, ctx, &value[1]); | 
|  |  | 
|  | if (ctx->value == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | ctx->net = net; | 
|  |  | 
|  | return ngx_http_geo_add_range(cf, ctx, start, end); | 
|  |  | 
|  | invalid: | 
|  |  | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net); | 
|  |  | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* the add procedure is optimized to add a growing up sequence */ | 
|  |  | 
|  | static char * | 
|  | ngx_http_geo_add_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | in_addr_t start, in_addr_t end) | 
|  | { | 
|  | in_addr_t              n; | 
|  | ngx_uint_t             h, i, s, e; | 
|  | ngx_array_t           *a; | 
|  | ngx_http_geo_range_t  *range; | 
|  |  | 
|  | for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) { | 
|  |  | 
|  | h = n >> 16; | 
|  |  | 
|  | if (n == start) { | 
|  | s = n & 0xffff; | 
|  | } else { | 
|  | s = 0; | 
|  | } | 
|  |  | 
|  | if ((n | 0xffff) > end) { | 
|  | e = end & 0xffff; | 
|  |  | 
|  | } else { | 
|  | e = 0xffff; | 
|  | } | 
|  |  | 
|  | a = (ngx_array_t *) ctx->high.low[h]; | 
|  |  | 
|  | if (a == NULL) { | 
|  | a = ngx_array_create(ctx->temp_pool, 64, | 
|  | sizeof(ngx_http_geo_range_t)); | 
|  | if (a == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | ctx->high.low[h] = (ngx_http_geo_range_t *) a; | 
|  | } | 
|  |  | 
|  | i = a->nelts; | 
|  | range = a->elts; | 
|  |  | 
|  | while (i) { | 
|  |  | 
|  | i--; | 
|  |  | 
|  | if (e < (ngx_uint_t) range[i].start) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (s > (ngx_uint_t) range[i].end) { | 
|  |  | 
|  | /* add after the range */ | 
|  |  | 
|  | range = ngx_array_push(a); | 
|  | if (range == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | range = a->elts; | 
|  |  | 
|  | ngx_memmove(&range[i + 2], &range[i + 1], | 
|  | (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t)); | 
|  |  | 
|  | range[i + 1].start = (u_short) s; | 
|  | range[i + 1].end = (u_short) e; | 
|  | range[i + 1].value = ctx->value; | 
|  |  | 
|  | goto next; | 
|  | } | 
|  |  | 
|  | if (s == (ngx_uint_t) range[i].start | 
|  | && e == (ngx_uint_t) range[i].end) | 
|  | { | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "duplicate range \"%V\", value: \"%v\", old value: \"%v\"", | 
|  | ctx->net, ctx->value, range[i].value); | 
|  |  | 
|  | range[i].value = ctx->value; | 
|  |  | 
|  | goto next; | 
|  | } | 
|  |  | 
|  | if (s > (ngx_uint_t) range[i].start | 
|  | && e < (ngx_uint_t) range[i].end) | 
|  | { | 
|  | /* split the range and insert the new one */ | 
|  |  | 
|  | range = ngx_array_push(a); | 
|  | if (range == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | range = ngx_array_push(a); | 
|  | if (range == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | range = a->elts; | 
|  |  | 
|  | ngx_memmove(&range[i + 3], &range[i + 1], | 
|  | (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t)); | 
|  |  | 
|  | range[i + 2].start = (u_short) (e + 1); | 
|  | range[i + 2].end = range[i].end; | 
|  | range[i + 2].value = range[i].value; | 
|  |  | 
|  | range[i + 1].start = (u_short) s; | 
|  | range[i + 1].end = (u_short) e; | 
|  | range[i + 1].value = ctx->value; | 
|  |  | 
|  | range[i].end = (u_short) (s - 1); | 
|  |  | 
|  | goto next; | 
|  | } | 
|  |  | 
|  | if (s == (ngx_uint_t) range[i].start | 
|  | && e < (ngx_uint_t) range[i].end) | 
|  | { | 
|  | /* shift the range start and insert the new range */ | 
|  |  | 
|  | range = ngx_array_push(a); | 
|  | if (range == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | range = a->elts; | 
|  |  | 
|  | ngx_memmove(&range[i + 1], &range[i], | 
|  | (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t)); | 
|  |  | 
|  | range[i + 1].start = (u_short) (e + 1); | 
|  |  | 
|  | range[i].start = (u_short) s; | 
|  | range[i].end = (u_short) e; | 
|  | range[i].value = ctx->value; | 
|  |  | 
|  | goto next; | 
|  | } | 
|  |  | 
|  | if (s > (ngx_uint_t) range[i].start | 
|  | && e == (ngx_uint_t) range[i].end) | 
|  | { | 
|  | /* shift the range end and insert the new range */ | 
|  |  | 
|  | range = ngx_array_push(a); | 
|  | if (range == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | range = a->elts; | 
|  |  | 
|  | ngx_memmove(&range[i + 2], &range[i + 1], | 
|  | (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t)); | 
|  |  | 
|  | range[i + 1].start = (u_short) s; | 
|  | range[i + 1].end = (u_short) e; | 
|  | range[i + 1].value = ctx->value; | 
|  |  | 
|  | range[i].end = (u_short) (s - 1); | 
|  |  | 
|  | goto next; | 
|  | } | 
|  |  | 
|  | s = (ngx_uint_t) range[i].start; | 
|  | e = (ngx_uint_t) range[i].end; | 
|  |  | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"", | 
|  | ctx->net, | 
|  | h >> 8, h & 0xff, s >> 8, s & 0xff, | 
|  | h >> 8, h & 0xff, e >> 8, e & 0xff); | 
|  |  | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | /* add the first range */ | 
|  |  | 
|  | range = ngx_array_push(a); | 
|  | if (range == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | range = a->elts; | 
|  |  | 
|  | ngx_memmove(&range[1], &range[0], | 
|  | (a->nelts - 1) * sizeof(ngx_http_geo_range_t)); | 
|  |  | 
|  | range[0].start = (u_short) s; | 
|  | range[0].end = (u_short) e; | 
|  | range[0].value = ctx->value; | 
|  |  | 
|  | next: | 
|  |  | 
|  | if (h == 0xffff) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_uint_t | 
|  | ngx_http_geo_delete_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | in_addr_t start, in_addr_t end) | 
|  | { | 
|  | in_addr_t              n; | 
|  | ngx_uint_t             h, i, s, e, warn; | 
|  | ngx_array_t           *a; | 
|  | ngx_http_geo_range_t  *range; | 
|  |  | 
|  | warn = 0; | 
|  |  | 
|  | for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) { | 
|  |  | 
|  | h = n >> 16; | 
|  |  | 
|  | if (n == start) { | 
|  | s = n & 0xffff; | 
|  | } else { | 
|  | s = 0; | 
|  | } | 
|  |  | 
|  | if ((n | 0xffff) > end) { | 
|  | e = end & 0xffff; | 
|  |  | 
|  | } else { | 
|  | e = 0xffff; | 
|  | } | 
|  |  | 
|  | a = (ngx_array_t *) ctx->high.low[h]; | 
|  |  | 
|  | if (a == NULL || a->nelts == 0) { | 
|  | warn = 1; | 
|  | goto next; | 
|  | } | 
|  |  | 
|  | range = a->elts; | 
|  | for (i = 0; i < a->nelts; i++) { | 
|  |  | 
|  | if (s == (ngx_uint_t) range[i].start | 
|  | && e == (ngx_uint_t) range[i].end) | 
|  | { | 
|  | ngx_memmove(&range[i], &range[i + 1], | 
|  | (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t)); | 
|  |  | 
|  | a->nelts--; | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (i == a->nelts - 1) { | 
|  | warn = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | next: | 
|  |  | 
|  | if (h == 0xffff) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return warn; | 
|  | } | 
|  |  | 
|  |  | 
|  | static char * | 
|  | ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_str_t *value) | 
|  | { | 
|  | char        *rv; | 
|  | ngx_int_t    rc, del; | 
|  | ngx_str_t   *net; | 
|  | ngx_cidr_t   cidr; | 
|  |  | 
|  | if (ctx->tree == NULL) { | 
|  | ctx->tree = ngx_radix_tree_create(ctx->pool, -1); | 
|  | if (ctx->tree == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | #if (NGX_HAVE_INET6) | 
|  | if (ctx->tree6 == NULL) { | 
|  | ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1); | 
|  | if (ctx->tree6 == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | if (ngx_strcmp(value[0].data, "default") == 0) { | 
|  | cidr.family = AF_INET; | 
|  | cidr.u.in.addr = 0; | 
|  | cidr.u.in.mask = 0; | 
|  |  | 
|  | rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]); | 
|  |  | 
|  | if (rv != NGX_CONF_OK) { | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | #if (NGX_HAVE_INET6) | 
|  | cidr.family = AF_INET6; | 
|  | ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t)); | 
|  |  | 
|  | rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]); | 
|  |  | 
|  | if (rv != NGX_CONF_OK) { | 
|  | return rv; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  | if (ngx_strcmp(value[0].data, "delete") == 0) { | 
|  | net = &value[1]; | 
|  | del = 1; | 
|  |  | 
|  | } else { | 
|  | net = &value[0]; | 
|  | del = 0; | 
|  | } | 
|  |  | 
|  | if (ngx_http_geo_cidr_value(cf, net, &cidr) != NGX_OK) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | if (cidr.family == AF_INET) { | 
|  | cidr.u.in.addr = ntohl(cidr.u.in.addr); | 
|  | cidr.u.in.mask = ntohl(cidr.u.in.mask); | 
|  | } | 
|  |  | 
|  | if (del) { | 
|  | switch (cidr.family) { | 
|  |  | 
|  | #if (NGX_HAVE_INET6) | 
|  | case AF_INET6: | 
|  | rc = ngx_radix128tree_delete(ctx->tree6, | 
|  | cidr.u.in6.addr.s6_addr, | 
|  | cidr.u.in6.mask.s6_addr); | 
|  | break; | 
|  | #endif | 
|  |  | 
|  | default: /* AF_INET */ | 
|  | rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr, | 
|  | cidr.u.in.mask); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (rc != NGX_OK) { | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "no network \"%V\" to delete", net); | 
|  | } | 
|  |  | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  | return ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], net); | 
|  | } | 
|  |  | 
|  |  | 
|  | static char * | 
|  | ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net) | 
|  | { | 
|  | ngx_int_t                   rc; | 
|  | ngx_http_variable_value_t  *val, *old; | 
|  |  | 
|  | val = ngx_http_geo_value(cf, ctx, value); | 
|  |  | 
|  | if (val == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | switch (cidr->family) { | 
|  |  | 
|  | #if (NGX_HAVE_INET6) | 
|  | case AF_INET6: | 
|  | rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr, | 
|  | cidr->u.in6.mask.s6_addr, | 
|  | (uintptr_t) val); | 
|  |  | 
|  | if (rc == NGX_OK) { | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  | if (rc == NGX_ERROR) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | /* rc == NGX_BUSY */ | 
|  |  | 
|  | old = (ngx_http_variable_value_t *) | 
|  | ngx_radix128tree_find(ctx->tree6, | 
|  | cidr->u.in6.addr.s6_addr); | 
|  |  | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "duplicate network \"%V\", value: \"%v\", old value: \"%v\"", | 
|  | net, val, old); | 
|  |  | 
|  | rc = ngx_radix128tree_delete(ctx->tree6, | 
|  | cidr->u.in6.addr.s6_addr, | 
|  | cidr->u.in6.mask.s6_addr); | 
|  |  | 
|  | if (rc == NGX_ERROR) { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr, | 
|  | cidr->u.in6.mask.s6_addr, | 
|  | (uintptr_t) val); | 
|  |  | 
|  | break; | 
|  | #endif | 
|  |  | 
|  | default: /* AF_INET */ | 
|  | rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr, | 
|  | cidr->u.in.mask, (uintptr_t) val); | 
|  |  | 
|  | if (rc == NGX_OK) { | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  | if (rc == NGX_ERROR) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | /* rc == NGX_BUSY */ | 
|  |  | 
|  | old = (ngx_http_variable_value_t *) | 
|  | ngx_radix32tree_find(ctx->tree, cidr->u.in.addr); | 
|  |  | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "duplicate network \"%V\", value: \"%v\", old value: \"%v\"", | 
|  | net, val, old); | 
|  |  | 
|  | rc = ngx_radix32tree_delete(ctx->tree, | 
|  | cidr->u.in.addr, cidr->u.in.mask); | 
|  |  | 
|  | if (rc == NGX_ERROR) { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr, | 
|  | cidr->u.in.mask, (uintptr_t) val); | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (rc == NGX_OK) { | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_http_variable_value_t * | 
|  | ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_str_t *value) | 
|  | { | 
|  | uint32_t                             hash; | 
|  | ngx_http_variable_value_t           *val; | 
|  | ngx_http_geo_variable_value_node_t  *gvvn; | 
|  |  | 
|  | hash = ngx_crc32_long(value->data, value->len); | 
|  |  | 
|  | gvvn = (ngx_http_geo_variable_value_node_t *) | 
|  | ngx_str_rbtree_lookup(&ctx->rbtree, value, hash); | 
|  |  | 
|  | if (gvvn) { | 
|  | return gvvn->value; | 
|  | } | 
|  |  | 
|  | val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t)); | 
|  | if (val == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | val->len = value->len; | 
|  | val->data = ngx_pstrdup(ctx->pool, value); | 
|  | if (val->data == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | val->valid = 1; | 
|  | val->no_cacheable = 0; | 
|  | val->not_found = 0; | 
|  |  | 
|  | gvvn = ngx_palloc(ctx->temp_pool, | 
|  | sizeof(ngx_http_geo_variable_value_node_t)); | 
|  | if (gvvn == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | gvvn->sn.node.key = hash; | 
|  | gvvn->sn.str.len = val->len; | 
|  | gvvn->sn.str.data = val->data; | 
|  | gvvn->value = val; | 
|  | gvvn->offset = 0; | 
|  |  | 
|  | ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node); | 
|  |  | 
|  | ctx->data_size += ngx_align(sizeof(ngx_http_variable_value_t) + value->len, | 
|  | sizeof(void *)); | 
|  |  | 
|  | return val; | 
|  | } | 
|  |  | 
|  |  | 
|  | static char * | 
|  | ngx_http_geo_add_proxy(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_cidr_t *cidr) | 
|  | { | 
|  | ngx_cidr_t  *c; | 
|  |  | 
|  | if (ctx->proxies == NULL) { | 
|  | ctx->proxies = ngx_array_create(ctx->pool, 4, sizeof(ngx_cidr_t)); | 
|  | if (ctx->proxies == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | c = ngx_array_push(ctx->proxies); | 
|  | if (c == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | *c = *cidr; | 
|  |  | 
|  | return NGX_CONF_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_geo_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 char * | 
|  | ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_str_t *name) | 
|  | { | 
|  | char       *rv; | 
|  | ngx_str_t   file; | 
|  |  | 
|  | file.len = name->len + 4; | 
|  | file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5); | 
|  | if (file.data == NULL) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | ngx_sprintf(file.data, "%V.bin%Z", name); | 
|  |  | 
|  | if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) { | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | if (ctx->ranges) { | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); | 
|  |  | 
|  | switch (ngx_http_geo_include_binary_base(cf, ctx, &file)) { | 
|  | case NGX_OK: | 
|  | return NGX_CONF_OK; | 
|  | case NGX_ERROR: | 
|  | return NGX_CONF_ERROR; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | file.len -= 4; | 
|  | file.data[file.len] = '\0'; | 
|  |  | 
|  | ctx->include_name = file; | 
|  |  | 
|  | if (ctx->outside_entries) { | 
|  | ctx->allow_binary_include = 0; | 
|  | } | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); | 
|  |  | 
|  | rv = ngx_conf_parse(cf, &file); | 
|  |  | 
|  | ctx->includes++; | 
|  | ctx->outside_entries = 0; | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_geo_include_binary_base(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, | 
|  | ngx_str_t *name) | 
|  | { | 
|  | u_char                     *base, ch; | 
|  | time_t                      mtime; | 
|  | size_t                      size, len; | 
|  | ssize_t                     n; | 
|  | uint32_t                    crc32; | 
|  | ngx_err_t                   err; | 
|  | ngx_int_t                   rc; | 
|  | ngx_uint_t                  i; | 
|  | ngx_file_t                  file; | 
|  | ngx_file_info_t             fi; | 
|  | ngx_http_geo_range_t       *range, **ranges; | 
|  | ngx_http_geo_header_t      *header; | 
|  | ngx_http_variable_value_t  *vv; | 
|  |  | 
|  | ngx_memzero(&file, sizeof(ngx_file_t)); | 
|  | file.name = *name; | 
|  | file.log = cf->log; | 
|  |  | 
|  | file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); | 
|  |  | 
|  | if (file.fd == NGX_INVALID_FILE) { | 
|  | err = ngx_errno; | 
|  | if (err != NGX_ENOENT) { | 
|  | ngx_conf_log_error(NGX_LOG_CRIT, cf, err, | 
|  | ngx_open_file_n " \"%s\" failed", name->data); | 
|  | } | 
|  | return NGX_DECLINED; | 
|  | } | 
|  |  | 
|  | if (ctx->outside_entries) { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "binary geo range base \"%s\" cannot be mixed with usual entries", | 
|  | name->data); | 
|  | rc = NGX_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | if (ctx->binary_include) { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "second binary geo range base \"%s\" cannot be mixed with \"%s\"", | 
|  | name->data, ctx->include_name.data); | 
|  | rc = NGX_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { | 
|  | ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, | 
|  | ngx_fd_info_n " \"%s\" failed", name->data); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | size = (size_t) ngx_file_size(&fi); | 
|  | mtime = ngx_file_mtime(&fi); | 
|  |  | 
|  | ch = name->data[name->len - 4]; | 
|  | name->data[name->len - 4] = '\0'; | 
|  |  | 
|  | if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { | 
|  | ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, | 
|  | ngx_file_info_n " \"%s\" failed", name->data); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | name->data[name->len - 4] = ch; | 
|  |  | 
|  | if (mtime < ngx_file_mtime(&fi)) { | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "stale binary geo range base \"%s\"", name->data); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | base = ngx_palloc(ctx->pool, size); | 
|  | if (base == NULL) { | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | n = ngx_read_file(&file, base, size, 0); | 
|  |  | 
|  | if (n == NGX_ERROR) { | 
|  | ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, | 
|  | ngx_read_file_n " \"%s\" failed", name->data); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | if ((size_t) n != size) { | 
|  | ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, | 
|  | ngx_read_file_n " \"%s\" returned only %z bytes instead of %z", | 
|  | name->data, n, size); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | header = (ngx_http_geo_header_t *) base; | 
|  |  | 
|  | if (size < 16 || ngx_memcmp(&ngx_http_geo_header, header, 12) != 0) { | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "incompatible binary geo range base \"%s\"", name->data); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | ngx_crc32_init(crc32); | 
|  |  | 
|  | vv = (ngx_http_variable_value_t *) (base + sizeof(ngx_http_geo_header_t)); | 
|  |  | 
|  | while (vv->data) { | 
|  | len = ngx_align(sizeof(ngx_http_variable_value_t) + vv->len, | 
|  | sizeof(void *)); | 
|  | ngx_crc32_update(&crc32, (u_char *) vv, len); | 
|  | vv->data += (size_t) base; | 
|  | vv = (ngx_http_variable_value_t *) ((u_char *) vv + len); | 
|  | } | 
|  | ngx_crc32_update(&crc32, (u_char *) vv, sizeof(ngx_http_variable_value_t)); | 
|  | vv++; | 
|  |  | 
|  | ranges = (ngx_http_geo_range_t **) vv; | 
|  |  | 
|  | for (i = 0; i < 0x10000; i++) { | 
|  | ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *)); | 
|  | if (ranges[i]) { | 
|  | ranges[i] = (ngx_http_geo_range_t *) | 
|  | ((u_char *) ranges[i] + (size_t) base); | 
|  | } | 
|  | } | 
|  |  | 
|  | range = (ngx_http_geo_range_t *) &ranges[0x10000]; | 
|  |  | 
|  | while ((u_char *) range < base + size) { | 
|  | while (range->value) { | 
|  | ngx_crc32_update(&crc32, (u_char *) range, | 
|  | sizeof(ngx_http_geo_range_t)); | 
|  | range->value = (ngx_http_variable_value_t *) | 
|  | ((u_char *) range->value + (size_t) base); | 
|  | range++; | 
|  | } | 
|  | ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *)); | 
|  | range = (ngx_http_geo_range_t *) ((u_char *) range + sizeof(void *)); | 
|  | } | 
|  |  | 
|  | ngx_crc32_final(crc32); | 
|  |  | 
|  | if (crc32 != header->crc32) { | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "CRC32 mismatch in binary geo range base \"%s\"", name->data); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, | 
|  | "using binary geo range base \"%s\"", name->data); | 
|  |  | 
|  | ctx->include_name = *name; | 
|  | ctx->binary_include = 1; | 
|  | ctx->high.low = ranges; | 
|  | rc = NGX_OK; | 
|  |  | 
|  | goto done; | 
|  |  | 
|  | failed: | 
|  |  | 
|  | rc = NGX_DECLINED; | 
|  |  | 
|  | done: | 
|  |  | 
|  | if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { | 
|  | ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, | 
|  | ngx_close_file_n " \"%s\" failed", name->data); | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx) | 
|  | { | 
|  | u_char                              *p; | 
|  | uint32_t                             hash; | 
|  | ngx_str_t                            s; | 
|  | ngx_uint_t                           i; | 
|  | ngx_file_mapping_t                   fm; | 
|  | ngx_http_geo_range_t                *r, *range, **ranges; | 
|  | ngx_http_geo_header_t               *header; | 
|  | ngx_http_geo_variable_value_node_t  *gvvn; | 
|  |  | 
|  | fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5); | 
|  | if (fm.name == NULL) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name); | 
|  |  | 
|  | fm.size = ctx->data_size; | 
|  | fm.log = ctx->pool->log; | 
|  |  | 
|  | ngx_log_error(NGX_LOG_NOTICE, fm.log, 0, | 
|  | "creating binary geo range base \"%s\"", fm.name); | 
|  |  | 
|  | if (ngx_create_file_mapping(&fm) != NGX_OK) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | p = ngx_cpymem(fm.addr, &ngx_http_geo_header, | 
|  | sizeof(ngx_http_geo_header_t)); | 
|  |  | 
|  | p = ngx_http_geo_copy_values(fm.addr, p, ctx->rbtree.root, | 
|  | ctx->rbtree.sentinel); | 
|  |  | 
|  | p += sizeof(ngx_http_variable_value_t); | 
|  |  | 
|  | ranges = (ngx_http_geo_range_t **) p; | 
|  |  | 
|  | p += 0x10000 * sizeof(ngx_http_geo_range_t *); | 
|  |  | 
|  | for (i = 0; i < 0x10000; i++) { | 
|  | r = ctx->high.low[i]; | 
|  | if (r == NULL) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | range = (ngx_http_geo_range_t *) p; | 
|  | ranges[i] = (ngx_http_geo_range_t *) (p - (u_char *) fm.addr); | 
|  |  | 
|  | do { | 
|  | s.len = r->value->len; | 
|  | s.data = r->value->data; | 
|  | hash = ngx_crc32_long(s.data, s.len); | 
|  | gvvn = (ngx_http_geo_variable_value_node_t *) | 
|  | ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash); | 
|  |  | 
|  | range->value = (ngx_http_variable_value_t *) gvvn->offset; | 
|  | range->start = r->start; | 
|  | range->end = r->end; | 
|  | range++; | 
|  |  | 
|  | } while ((++r)->value); | 
|  |  | 
|  | range->value = NULL; | 
|  |  | 
|  | p = (u_char *) range + sizeof(void *); | 
|  | } | 
|  |  | 
|  | header = fm.addr; | 
|  | header->crc32 = ngx_crc32_long((u_char *) fm.addr | 
|  | + sizeof(ngx_http_geo_header_t), | 
|  | fm.size - sizeof(ngx_http_geo_header_t)); | 
|  |  | 
|  | ngx_close_file_mapping(&fm); | 
|  | } | 
|  |  | 
|  |  | 
|  | static u_char * | 
|  | ngx_http_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node, | 
|  | ngx_rbtree_node_t *sentinel) | 
|  | { | 
|  | ngx_http_variable_value_t           *vv; | 
|  | ngx_http_geo_variable_value_node_t  *gvvn; | 
|  |  | 
|  | if (node == sentinel) { | 
|  | return p; | 
|  | } | 
|  |  | 
|  | gvvn = (ngx_http_geo_variable_value_node_t *) node; | 
|  | gvvn->offset = p - base; | 
|  |  | 
|  | vv = (ngx_http_variable_value_t *) p; | 
|  | *vv = *gvvn->value; | 
|  | p += sizeof(ngx_http_variable_value_t); | 
|  | vv->data = (u_char *) (p - base); | 
|  |  | 
|  | p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len); | 
|  |  | 
|  | p = ngx_align_ptr(p, sizeof(void *)); | 
|  |  | 
|  | p = ngx_http_geo_copy_values(base, p, node->left, sentinel); | 
|  |  | 
|  | return ngx_http_geo_copy_values(base, p, node->right, sentinel); | 
|  | } |