aboutsummaryrefslogtreecommitdiffstats
path: root/app/nginx/src/stream/ngx_stream_geo_module.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/nginx/src/stream/ngx_stream_geo_module.c')
-rw-r--r--app/nginx/src/stream/ngx_stream_geo_module.c1586
1 files changed, 1586 insertions, 0 deletions
diff --git a/app/nginx/src/stream/ngx_stream_geo_module.c b/app/nginx/src/stream/ngx_stream_geo_module.c
new file mode 100644
index 0000000..2204546
--- /dev/null
+++ b/app/nginx/src/stream/ngx_stream_geo_module.c
@@ -0,0 +1,1586 @@
+
+/*
+ * 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);
+}