| /* | |
| * Copyright (C) Igor Sysoev | |
| * Copyright (C) Nginx, Inc. | |
| */ | |
| #include <ngx_config.h> | |
| #include <ngx_core.h> | |
| #include <ngx_stream.h> | |
| typedef struct { | |
| ngx_stream_variable_value_t *value; | |
| u_short start; | |
| u_short end; | |
| } ngx_stream_geo_range_t; | |
| typedef struct { | |
| ngx_radix_tree_t *tree; | |
| #if (NGX_HAVE_INET6) | |
| ngx_radix_tree_t *tree6; | |
| #endif | |
| } ngx_stream_geo_trees_t; | |
| typedef struct { | |
| ngx_stream_geo_range_t **low; | |
| ngx_stream_variable_value_t *default_value; | |
| } ngx_stream_geo_high_ranges_t; | |
| typedef struct { | |
| ngx_str_node_t sn; | |
| ngx_stream_variable_value_t *value; | |
| size_t offset; | |
| } ngx_stream_geo_variable_value_node_t; | |
| typedef struct { | |
| ngx_stream_variable_value_t *value; | |
| ngx_str_t *net; | |
| ngx_stream_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_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; | |
| } ngx_stream_geo_conf_ctx_t; | |
| typedef struct { | |
| union { | |
| ngx_stream_geo_trees_t trees; | |
| ngx_stream_geo_high_ranges_t high; | |
| } u; | |
| ngx_int_t index; | |
| } ngx_stream_geo_ctx_t; | |
| static ngx_int_t ngx_stream_geo_addr(ngx_stream_session_t *s, | |
| ngx_stream_geo_ctx_t *ctx, ngx_addr_t *addr); | |
| static char *ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, | |
| void *conf); | |
| static char *ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf); | |
| static char *ngx_stream_geo_range(ngx_conf_t *cf, | |
| ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value); | |
| static char *ngx_stream_geo_add_range(ngx_conf_t *cf, | |
| ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end); | |
| static ngx_uint_t ngx_stream_geo_delete_range(ngx_conf_t *cf, | |
| ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end); | |
| static char *ngx_stream_geo_cidr(ngx_conf_t *cf, | |
| ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value); | |
| static char *ngx_stream_geo_cidr_add(ngx_conf_t *cf, | |
| ngx_stream_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr, ngx_str_t *value, | |
| ngx_str_t *net); | |
| static ngx_stream_variable_value_t *ngx_stream_geo_value(ngx_conf_t *cf, | |
| ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value); | |
| static ngx_int_t ngx_stream_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, | |
| ngx_cidr_t *cidr); | |
| static char *ngx_stream_geo_include(ngx_conf_t *cf, | |
| ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name); | |
| static ngx_int_t ngx_stream_geo_include_binary_base(ngx_conf_t *cf, | |
| ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name); | |
| static void ngx_stream_geo_create_binary_base(ngx_stream_geo_conf_ctx_t *ctx); | |
| static u_char *ngx_stream_geo_copy_values(u_char *base, u_char *p, | |
| ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); | |
| static ngx_command_t ngx_stream_geo_commands[] = { | |
| { ngx_string("geo"), | |
| NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12, | |
| ngx_stream_geo_block, | |
| 0, | |
| 0, | |
| NULL }, | |
| ngx_null_command | |
| }; | |
| static ngx_stream_module_t ngx_stream_geo_module_ctx = { | |
| NULL, /* preconfiguration */ | |
| NULL, /* postconfiguration */ | |
| NULL, /* create main configuration */ | |
| NULL, /* init main configuration */ | |
| NULL, /* create server configuration */ | |
| NULL /* merge server configuration */ | |
| }; | |
| ngx_module_t ngx_stream_geo_module = { | |
| NGX_MODULE_V1, | |
| &ngx_stream_geo_module_ctx, /* module context */ | |
| ngx_stream_geo_commands, /* module directives */ | |
| NGX_STREAM_MODULE, /* module type */ | |
| NULL, /* init master */ | |
| NULL, /* init module */ | |
| NULL, /* init process */ | |
| NULL, /* init thread */ | |
| NULL, /* exit thread */ | |
| NULL, /* exit process */ | |
| NULL, /* exit master */ | |
| NGX_MODULE_V1_PADDING | |
| }; | |
| typedef struct { | |
| u_char GEORNG[6]; | |
| u_char version; | |
| u_char ptr_size; | |
| uint32_t endianness; | |
| uint32_t crc32; | |
| } ngx_stream_geo_header_t; | |
| static ngx_stream_geo_header_t ngx_stream_geo_header = { | |
| { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0 | |
| }; | |
| /* geo range is AF_INET only */ | |
| static ngx_int_t | |
| ngx_stream_geo_cidr_variable(ngx_stream_session_t *s, | |
| ngx_stream_variable_value_t *v, uintptr_t data) | |
| { | |
| ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data; | |
| in_addr_t inaddr; | |
| ngx_addr_t addr; | |
| struct sockaddr_in *sin; | |
| ngx_stream_variable_value_t *vv; | |
| #if (NGX_HAVE_INET6) | |
| u_char *p; | |
| struct in6_addr *inaddr6; | |
| #endif | |
| if (ngx_stream_geo_addr(s, ctx, &addr) != NGX_OK) { | |
| vv = (ngx_stream_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_stream_variable_value_t *) | |
| ngx_radix32tree_find(ctx->u.trees.tree, inaddr); | |
| } else { | |
| vv = (ngx_stream_variable_value_t *) | |
| ngx_radix128tree_find(ctx->u.trees.tree6, p); | |
| } | |
| break; | |
| #endif | |
| default: /* AF_INET */ | |
| sin = (struct sockaddr_in *) addr.sockaddr; | |
| inaddr = ntohl(sin->sin_addr.s_addr); | |
| vv = (ngx_stream_variable_value_t *) | |
| ngx_radix32tree_find(ctx->u.trees.tree, inaddr); | |
| break; | |
| } | |
| done: | |
| *v = *vv; | |
| ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, | |
| "stream geo: %v", v); | |
| return NGX_OK; | |
| } | |
| static ngx_int_t | |
| ngx_stream_geo_range_variable(ngx_stream_session_t *s, | |
| ngx_stream_variable_value_t *v, uintptr_t data) | |
| { | |
| ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data; | |
| in_addr_t inaddr; | |
| ngx_addr_t addr; | |
| ngx_uint_t n; | |
| struct sockaddr_in *sin; | |
| ngx_stream_geo_range_t *range; | |
| #if (NGX_HAVE_INET6) | |
| u_char *p; | |
| struct in6_addr *inaddr6; | |
| #endif | |
| *v = *ctx->u.high.default_value; | |
| if (ngx_stream_geo_addr(s, 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 | |
| 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_STREAM, s->connection->log, 0, | |
| "stream geo: %v", v); | |
| return NGX_OK; | |
| } | |
| static ngx_int_t | |
| ngx_stream_geo_addr(ngx_stream_session_t *s, ngx_stream_geo_ctx_t *ctx, | |
| ngx_addr_t *addr) | |
| { | |
| ngx_stream_variable_value_t *v; | |
| if (ctx->index == -1) { | |
| ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, | |
| "stream geo started: %V", &s->connection->addr_text); | |
| addr->sockaddr = s->connection->sockaddr; | |
| addr->socklen = s->connection->socklen; | |
| /* addr->name = s->connection->addr_text; */ | |
| return NGX_OK; | |
| } | |
| v = ngx_stream_get_flushed_variable(s, ctx->index); | |
| if (v == NULL || v->not_found) { | |
| ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, | |
| "stream geo not found"); | |
| return NGX_ERROR; | |
| } | |
| ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, | |
| "stream geo started: %v", v); | |
| if (ngx_parse_addr(s->connection->pool, addr, v->data, v->len) == NGX_OK) { | |
| return NGX_OK; | |
| } | |
| return NGX_ERROR; | |
| } | |
| static char * | |
| ngx_stream_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_stream_variable_t *var; | |
| ngx_stream_geo_ctx_t *geo; | |
| ngx_stream_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_stream_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_stream_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_stream_add_variable(cf, &name, NGX_STREAM_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_stream_geo_conf_ctx_t)); | |
| ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); | |
| if (ctx.temp_pool == NULL) { | |
| 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_stream_geo_header_t) | |
| + sizeof(ngx_stream_variable_value_t) | |
| + 0x10000 * sizeof(ngx_stream_geo_range_t *); | |
| ctx.allow_binary_include = 1; | |
| save = *cf; | |
| cf->pool = pool; | |
| cf->ctx = &ctx; | |
| cf->handler = ngx_stream_geo; | |
| cf->handler_conf = conf; | |
| rv = ngx_conf_parse(cf, NULL); | |
| *cf = save; | |
| 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_stream_geo_range_t); | |
| ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); | |
| if (ctx.high.low[i] == NULL) { | |
| return NGX_CONF_ERROR; | |
| } | |
| 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_stream_geo_create_binary_base(&ctx); | |
| } | |
| } | |
| if (ctx.high.default_value == NULL) { | |
| ctx.high.default_value = &ngx_stream_variable_null_value; | |
| } | |
| geo->u.high = ctx.high; | |
| var->get_handler = ngx_stream_geo_range_variable; | |
| var->data = (uintptr_t) geo; | |
| ngx_destroy_pool(ctx.temp_pool); | |
| ngx_destroy_pool(pool); | |
| } else { | |
| if (ctx.tree == NULL) { | |
| ctx.tree = ngx_radix_tree_create(cf->pool, -1); | |
| if (ctx.tree == NULL) { | |
| return NGX_CONF_ERROR; | |
| } | |
| } | |
| 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) { | |
| return NGX_CONF_ERROR; | |
| } | |
| } | |
| geo->u.trees.tree6 = ctx.tree6; | |
| #endif | |
| var->get_handler = ngx_stream_geo_cidr_variable; | |
| var->data = (uintptr_t) geo; | |
| ngx_destroy_pool(ctx.temp_pool); | |
| ngx_destroy_pool(pool); | |
| if (ngx_radix32tree_insert(ctx.tree, 0, 0, | |
| (uintptr_t) &ngx_stream_variable_null_value) | |
| == NGX_ERROR) | |
| { | |
| return NGX_CONF_ERROR; | |
| } | |
| /* 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_stream_variable_null_value) | |
| == NGX_ERROR) | |
| { | |
| return NGX_CONF_ERROR; | |
| } | |
| #endif | |
| } | |
| return rv; | |
| } | |
| static char * | |
| ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) | |
| { | |
| char *rv; | |
| ngx_str_t *value; | |
| ngx_stream_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; | |
| } | |
| } | |
| 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_stream_geo_include(cf, ctx, &value[1]); | |
| goto done; | |
| } | |
| if (ctx->ranges) { | |
| rv = ngx_stream_geo_range(cf, ctx, value); | |
| } else { | |
| rv = ngx_stream_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_stream_geo_range(ngx_conf_t *cf, ngx_stream_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_stream_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_stream_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_stream_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_stream_geo_value(cf, ctx, &value[1]); | |
| if (ctx->value == NULL) { | |
| return NGX_CONF_ERROR; | |
| } | |
| ctx->net = net; | |
| return ngx_stream_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_stream_geo_add_range(ngx_conf_t *cf, ngx_stream_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_stream_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_stream_geo_range_t)); | |
| if (a == NULL) { | |
| return NGX_CONF_ERROR; | |
| } | |
| ctx->high.low[h] = (ngx_stream_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_stream_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_stream_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_stream_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_stream_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_stream_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_stream_geo_delete_range(ngx_conf_t *cf, ngx_stream_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_stream_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_stream_geo_range_t)); | |
| a->nelts--; | |
| break; | |
| } | |
| if (i == a->nelts - 1) { | |
| warn = 1; | |
| } | |
| } | |
| next: | |
| if (h == 0xffff) { | |
| break; | |
| } | |
| } | |
| return warn; | |
| } | |
| static char * | |
| ngx_stream_geo_cidr(ngx_conf_t *cf, ngx_stream_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_stream_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_stream_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_stream_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_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], net); | |
| } | |
| static char * | |
| ngx_stream_geo_cidr_add(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, | |
| ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net) | |
| { | |
| ngx_int_t rc; | |
| ngx_stream_variable_value_t *val, *old; | |
| val = ngx_stream_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_stream_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_stream_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_stream_variable_value_t * | |
| ngx_stream_geo_value(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, | |
| ngx_str_t *value) | |
| { | |
| uint32_t hash; | |
| ngx_stream_variable_value_t *val; | |
| ngx_stream_geo_variable_value_node_t *gvvn; | |
| hash = ngx_crc32_long(value->data, value->len); | |
| gvvn = (ngx_stream_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_stream_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_stream_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_stream_variable_value_t) | |
| + value->len, sizeof(void *)); | |
| return val; | |
| } | |
| static ngx_int_t | |
| ngx_stream_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_stream_geo_include(ngx_conf_t *cf, ngx_stream_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_stream_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_stream_geo_include_binary_base(ngx_conf_t *cf, | |
| ngx_stream_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_stream_geo_range_t *range, **ranges; | |
| ngx_stream_geo_header_t *header; | |
| ngx_stream_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, 0, 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_stream_geo_header_t *) base; | |
| if (size < 16 || ngx_memcmp(&ngx_stream_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_stream_variable_value_t *) | |
| (base + sizeof(ngx_stream_geo_header_t)); | |
| while (vv->data) { | |
| len = ngx_align(sizeof(ngx_stream_variable_value_t) + vv->len, | |
| sizeof(void *)); | |
| ngx_crc32_update(&crc32, (u_char *) vv, len); | |
| vv->data += (size_t) base; | |
| vv = (ngx_stream_variable_value_t *) ((u_char *) vv + len); | |
| } | |
| ngx_crc32_update(&crc32, (u_char *) vv, | |
| sizeof(ngx_stream_variable_value_t)); | |
| vv++; | |
| ranges = (ngx_stream_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_stream_geo_range_t *) | |
| ((u_char *) ranges[i] + (size_t) base); | |
| } | |
| } | |
| range = (ngx_stream_geo_range_t *) &ranges[0x10000]; | |
| while ((u_char *) range < base + size) { | |
| while (range->value) { | |
| ngx_crc32_update(&crc32, (u_char *) range, | |
| sizeof(ngx_stream_geo_range_t)); | |
| range->value = (ngx_stream_variable_value_t *) | |
| ((u_char *) range->value + (size_t) base); | |
| range++; | |
| } | |
| ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *)); | |
| range = (ngx_stream_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_stream_geo_create_binary_base(ngx_stream_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_stream_geo_range_t *r, *range, **ranges; | |
| ngx_stream_geo_header_t *header; | |
| ngx_stream_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_stream_geo_header, | |
| sizeof(ngx_stream_geo_header_t)); | |
| p = ngx_stream_geo_copy_values(fm.addr, p, ctx->rbtree.root, | |
| ctx->rbtree.sentinel); | |
| p += sizeof(ngx_stream_variable_value_t); | |
| ranges = (ngx_stream_geo_range_t **) p; | |
| p += 0x10000 * sizeof(ngx_stream_geo_range_t *); | |
| for (i = 0; i < 0x10000; i++) { | |
| r = ctx->high.low[i]; | |
| if (r == NULL) { | |
| continue; | |
| } | |
| range = (ngx_stream_geo_range_t *) p; | |
| ranges[i] = (ngx_stream_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_stream_geo_variable_value_node_t *) | |
| ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash); | |
| range->value = (ngx_stream_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_stream_geo_header_t), | |
| fm.size - sizeof(ngx_stream_geo_header_t)); | |
| ngx_close_file_mapping(&fm); | |
| } | |
| static u_char * | |
| ngx_stream_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node, | |
| ngx_rbtree_node_t *sentinel) | |
| { | |
| ngx_stream_variable_value_t *vv; | |
| ngx_stream_geo_variable_value_node_t *gvvn; | |
| if (node == sentinel) { | |
| return p; | |
| } | |
| gvvn = (ngx_stream_geo_variable_value_node_t *) node; | |
| gvvn->offset = p - base; | |
| vv = (ngx_stream_variable_value_t *) p; | |
| *vv = *gvvn->value; | |
| p += sizeof(ngx_stream_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_stream_geo_copy_values(base, p, node->left, sentinel); | |
| return ngx_stream_geo_copy_values(base, p, node->right, sentinel); | |
| } |