1
2/*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8#include <ngx_config.h>
9#include <ngx_core.h>
10#include <ngx_stream.h>
11
12
13typedef struct {
14    ngx_stream_variable_value_t       *value;
15    u_short                            start;
16    u_short                            end;
17} ngx_stream_geo_range_t;
18
19
20typedef struct {
21    ngx_radix_tree_t                  *tree;
22#if (NGX_HAVE_INET6)
23    ngx_radix_tree_t                  *tree6;
24#endif
25} ngx_stream_geo_trees_t;
26
27
28typedef struct {
29    ngx_stream_geo_range_t           **low;
30    ngx_stream_variable_value_t       *default_value;
31} ngx_stream_geo_high_ranges_t;
32
33
34typedef struct {
35    ngx_str_node_t                     sn;
36    ngx_stream_variable_value_t       *value;
37    size_t                             offset;
38} ngx_stream_geo_variable_value_node_t;
39
40
41typedef struct {
42    ngx_stream_variable_value_t       *value;
43    ngx_str_t                         *net;
44    ngx_stream_geo_high_ranges_t       high;
45    ngx_radix_tree_t                  *tree;
46#if (NGX_HAVE_INET6)
47    ngx_radix_tree_t                  *tree6;
48#endif
49    ngx_rbtree_t                       rbtree;
50    ngx_rbtree_node_t                  sentinel;
51    ngx_pool_t                        *pool;
52    ngx_pool_t                        *temp_pool;
53
54    size_t                             data_size;
55
56    ngx_str_t                          include_name;
57    ngx_uint_t                         includes;
58    ngx_uint_t                         entries;
59
60    unsigned                           ranges:1;
61    unsigned                           outside_entries:1;
62    unsigned                           allow_binary_include:1;
63    unsigned                           binary_include:1;
64} ngx_stream_geo_conf_ctx_t;
65
66
67typedef struct {
68    union {
69        ngx_stream_geo_trees_t         trees;
70        ngx_stream_geo_high_ranges_t   high;
71    } u;
72
73    ngx_int_t                          index;
74} ngx_stream_geo_ctx_t;
75
76
77static ngx_int_t ngx_stream_geo_addr(ngx_stream_session_t *s,
78    ngx_stream_geo_ctx_t *ctx, ngx_addr_t *addr);
79
80static char *ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd,
81    void *conf);
82static char *ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
83static char *ngx_stream_geo_range(ngx_conf_t *cf,
84    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
85static char *ngx_stream_geo_add_range(ngx_conf_t *cf,
86    ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
87static ngx_uint_t ngx_stream_geo_delete_range(ngx_conf_t *cf,
88    ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
89static char *ngx_stream_geo_cidr(ngx_conf_t *cf,
90    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
91static char *ngx_stream_geo_cidr_add(ngx_conf_t *cf,
92    ngx_stream_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr, ngx_str_t *value,
93    ngx_str_t *net);
94static ngx_stream_variable_value_t *ngx_stream_geo_value(ngx_conf_t *cf,
95    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
96static ngx_int_t ngx_stream_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net,
97    ngx_cidr_t *cidr);
98static char *ngx_stream_geo_include(ngx_conf_t *cf,
99    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name);
100static ngx_int_t ngx_stream_geo_include_binary_base(ngx_conf_t *cf,
101    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name);
102static void ngx_stream_geo_create_binary_base(ngx_stream_geo_conf_ctx_t *ctx);
103static u_char *ngx_stream_geo_copy_values(u_char *base, u_char *p,
104    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
105
106
107static ngx_command_t  ngx_stream_geo_commands[] = {
108
109    { ngx_string("geo"),
110      NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
111      ngx_stream_geo_block,
112      0,
113      0,
114      NULL },
115
116      ngx_null_command
117};
118
119
120static ngx_stream_module_t  ngx_stream_geo_module_ctx = {
121    NULL,                                  /* preconfiguration */
122    NULL,                                  /* postconfiguration */
123
124    NULL,                                  /* create main configuration */
125    NULL,                                  /* init main configuration */
126
127    NULL,                                  /* create server configuration */
128    NULL                                   /* merge server configuration */
129};
130
131
132ngx_module_t  ngx_stream_geo_module = {
133    NGX_MODULE_V1,
134    &ngx_stream_geo_module_ctx,            /* module context */
135    ngx_stream_geo_commands,               /* module directives */
136    NGX_STREAM_MODULE,                     /* module type */
137    NULL,                                  /* init master */
138    NULL,                                  /* init module */
139    NULL,                                  /* init process */
140    NULL,                                  /* init thread */
141    NULL,                                  /* exit thread */
142    NULL,                                  /* exit process */
143    NULL,                                  /* exit master */
144    NGX_MODULE_V1_PADDING
145};
146
147
148typedef struct {
149    u_char    GEORNG[6];
150    u_char    version;
151    u_char    ptr_size;
152    uint32_t  endianness;
153    uint32_t  crc32;
154} ngx_stream_geo_header_t;
155
156
157static ngx_stream_geo_header_t  ngx_stream_geo_header = {
158    { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0
159};
160
161
162/* geo range is AF_INET only */
163
164static ngx_int_t
165ngx_stream_geo_cidr_variable(ngx_stream_session_t *s,
166    ngx_stream_variable_value_t *v, uintptr_t data)
167{
168    ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data;
169
170    in_addr_t                     inaddr;
171    ngx_addr_t                    addr;
172    struct sockaddr_in           *sin;
173    ngx_stream_variable_value_t  *vv;
174#if (NGX_HAVE_INET6)
175    u_char                       *p;
176    struct in6_addr              *inaddr6;
177#endif
178
179    if (ngx_stream_geo_addr(s, ctx, &addr) != NGX_OK) {
180        vv = (ngx_stream_variable_value_t *)
181                  ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE);
182        goto done;
183    }
184
185    switch (addr.sockaddr->sa_family) {
186
187#if (NGX_HAVE_INET6)
188    case AF_INET6:
189        inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
190        p = inaddr6->s6_addr;
191
192        if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
193            inaddr = p[12] << 24;
194            inaddr += p[13] << 16;
195            inaddr += p[14] << 8;
196            inaddr += p[15];
197
198            vv = (ngx_stream_variable_value_t *)
199                      ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
200
201        } else {
202            vv = (ngx_stream_variable_value_t *)
203                      ngx_radix128tree_find(ctx->u.trees.tree6, p);
204        }
205
206        break;
207#endif
208
209    default: /* AF_INET */
210        sin = (struct sockaddr_in *) addr.sockaddr;
211        inaddr = ntohl(sin->sin_addr.s_addr);
212
213        vv = (ngx_stream_variable_value_t *)
214                  ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
215
216        break;
217    }
218
219done:
220
221    *v = *vv;
222
223    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
224                   "stream geo: %v", v);
225
226    return NGX_OK;
227}
228
229
230static ngx_int_t
231ngx_stream_geo_range_variable(ngx_stream_session_t *s,
232    ngx_stream_variable_value_t *v, uintptr_t data)
233{
234    ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data;
235
236    in_addr_t                inaddr;
237    ngx_addr_t               addr;
238    ngx_uint_t               n;
239    struct sockaddr_in      *sin;
240    ngx_stream_geo_range_t  *range;
241#if (NGX_HAVE_INET6)
242    u_char                  *p;
243    struct in6_addr         *inaddr6;
244#endif
245
246    *v = *ctx->u.high.default_value;
247
248    if (ngx_stream_geo_addr(s, ctx, &addr) == NGX_OK) {
249
250        switch (addr.sockaddr->sa_family) {
251
252#if (NGX_HAVE_INET6)
253        case AF_INET6:
254            inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
255
256            if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
257                p = inaddr6->s6_addr;
258
259                inaddr = p[12] << 24;
260                inaddr += p[13] << 16;
261                inaddr += p[14] << 8;
262                inaddr += p[15];
263
264            } else {
265                inaddr = INADDR_NONE;
266            }
267
268            break;
269#endif
270
271        default: /* AF_INET */
272            sin = (struct sockaddr_in *) addr.sockaddr;
273            inaddr = ntohl(sin->sin_addr.s_addr);
274            break;
275        }
276
277    } else {
278        inaddr = INADDR_NONE;
279    }
280
281    if (ctx->u.high.low) {
282        range = ctx->u.high.low[inaddr >> 16];
283
284        if (range) {
285            n = inaddr & 0xffff;
286            do {
287                if (n >= (ngx_uint_t) range->start
288                    && n <= (ngx_uint_t) range->end)
289                {
290                    *v = *range->value;
291                    break;
292                }
293            } while ((++range)->value);
294        }
295    }
296
297    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
298                   "stream geo: %v", v);
299
300    return NGX_OK;
301}
302
303
304static ngx_int_t
305ngx_stream_geo_addr(ngx_stream_session_t *s, ngx_stream_geo_ctx_t *ctx,
306    ngx_addr_t *addr)
307{
308    ngx_stream_variable_value_t  *v;
309
310    if (ctx->index == -1) {
311        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
312                       "stream geo started: %V", &s->connection->addr_text);
313
314        addr->sockaddr = s->connection->sockaddr;
315        addr->socklen = s->connection->socklen;
316        /* addr->name = s->connection->addr_text; */
317
318        return NGX_OK;
319    }
320
321    v = ngx_stream_get_flushed_variable(s, ctx->index);
322
323    if (v == NULL || v->not_found) {
324        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
325                       "stream geo not found");
326
327        return NGX_ERROR;
328    }
329
330    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
331                   "stream geo started: %v", v);
332
333    if (ngx_parse_addr(s->connection->pool, addr, v->data, v->len) == NGX_OK) {
334        return NGX_OK;
335    }
336
337    return NGX_ERROR;
338}
339
340
341static char *
342ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
343{
344    char                         *rv;
345    size_t                        len;
346    ngx_str_t                    *value, name;
347    ngx_uint_t                    i;
348    ngx_conf_t                    save;
349    ngx_pool_t                   *pool;
350    ngx_array_t                  *a;
351    ngx_stream_variable_t        *var;
352    ngx_stream_geo_ctx_t         *geo;
353    ngx_stream_geo_conf_ctx_t     ctx;
354#if (NGX_HAVE_INET6)
355    static struct in6_addr        zero;
356#endif
357
358    value = cf->args->elts;
359
360    geo = ngx_palloc(cf->pool, sizeof(ngx_stream_geo_ctx_t));
361    if (geo == NULL) {
362        return NGX_CONF_ERROR;
363    }
364
365    name = value[1];
366
367    if (name.data[0] != '$') {
368        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
369                           "invalid variable name \"%V\"", &name);
370        return NGX_CONF_ERROR;
371    }
372
373    name.len--;
374    name.data++;
375
376    if (cf->args->nelts == 3) {
377
378        geo->index = ngx_stream_get_variable_index(cf, &name);
379        if (geo->index == NGX_ERROR) {
380            return NGX_CONF_ERROR;
381        }
382
383        name = value[2];
384
385        if (name.data[0] != '$') {
386            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
387                               "invalid variable name \"%V\"", &name);
388            return NGX_CONF_ERROR;
389        }
390
391        name.len--;
392        name.data++;
393
394    } else {
395        geo->index = -1;
396    }
397
398    var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
399    if (var == NULL) {
400        return NGX_CONF_ERROR;
401    }
402
403    pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
404    if (pool == NULL) {
405        return NGX_CONF_ERROR;
406    }
407
408    ngx_memzero(&ctx, sizeof(ngx_stream_geo_conf_ctx_t));
409
410    ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
411    if (ctx.temp_pool == NULL) {
412        return NGX_CONF_ERROR;
413    }
414
415    ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value);
416
417    ctx.pool = cf->pool;
418    ctx.data_size = sizeof(ngx_stream_geo_header_t)
419                  + sizeof(ngx_stream_variable_value_t)
420                  + 0x10000 * sizeof(ngx_stream_geo_range_t *);
421    ctx.allow_binary_include = 1;
422
423    save = *cf;
424    cf->pool = pool;
425    cf->ctx = &ctx;
426    cf->handler = ngx_stream_geo;
427    cf->handler_conf = conf;
428
429    rv = ngx_conf_parse(cf, NULL);
430
431    *cf = save;
432
433    if (ctx.ranges) {
434
435        if (ctx.high.low && !ctx.binary_include) {
436            for (i = 0; i < 0x10000; i++) {
437                a = (ngx_array_t *) ctx.high.low[i];
438
439                if (a == NULL) {
440                    continue;
441                }
442
443                if (a->nelts == 0) {
444                    ctx.high.low[i] = NULL;
445                    continue;
446                }
447
448                len = a->nelts * sizeof(ngx_stream_geo_range_t);
449
450                ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *));
451                if (ctx.high.low[i] == NULL) {
452                    return NGX_CONF_ERROR;
453                }
454
455                ngx_memcpy(ctx.high.low[i], a->elts, len);
456                ctx.high.low[i][a->nelts].value = NULL;
457                ctx.data_size += len + sizeof(void *);
458            }
459
460            if (ctx.allow_binary_include
461                && !ctx.outside_entries
462                && ctx.entries > 100000
463                && ctx.includes == 1)
464            {
465                ngx_stream_geo_create_binary_base(&ctx);
466            }
467        }
468
469        if (ctx.high.default_value == NULL) {
470            ctx.high.default_value = &ngx_stream_variable_null_value;
471        }
472
473        geo->u.high = ctx.high;
474
475        var->get_handler = ngx_stream_geo_range_variable;
476        var->data = (uintptr_t) geo;
477
478        ngx_destroy_pool(ctx.temp_pool);
479        ngx_destroy_pool(pool);
480
481    } else {
482        if (ctx.tree == NULL) {
483            ctx.tree = ngx_radix_tree_create(cf->pool, -1);
484            if (ctx.tree == NULL) {
485                return NGX_CONF_ERROR;
486            }
487        }
488
489        geo->u.trees.tree = ctx.tree;
490
491#if (NGX_HAVE_INET6)
492        if (ctx.tree6 == NULL) {
493            ctx.tree6 = ngx_radix_tree_create(cf->pool, -1);
494            if (ctx.tree6 == NULL) {
495                return NGX_CONF_ERROR;
496            }
497        }
498
499        geo->u.trees.tree6 = ctx.tree6;
500#endif
501
502        var->get_handler = ngx_stream_geo_cidr_variable;
503        var->data = (uintptr_t) geo;
504
505        ngx_destroy_pool(ctx.temp_pool);
506        ngx_destroy_pool(pool);
507
508        if (ngx_radix32tree_insert(ctx.tree, 0, 0,
509                                   (uintptr_t) &ngx_stream_variable_null_value)
510            == NGX_ERROR)
511        {
512            return NGX_CONF_ERROR;
513        }
514
515        /* NGX_BUSY is okay (default was set explicitly) */
516
517#if (NGX_HAVE_INET6)
518        if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr,
519                                    (uintptr_t) &ngx_stream_variable_null_value)
520            == NGX_ERROR)
521        {
522            return NGX_CONF_ERROR;
523        }
524#endif
525    }
526
527    return rv;
528}
529
530
531static char *
532ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
533{
534    char                       *rv;
535    ngx_str_t                  *value;
536    ngx_stream_geo_conf_ctx_t  *ctx;
537
538    ctx = cf->ctx;
539
540    value = cf->args->elts;
541
542    if (cf->args->nelts == 1) {
543
544        if (ngx_strcmp(value[0].data, "ranges") == 0) {
545
546            if (ctx->tree
547#if (NGX_HAVE_INET6)
548                || ctx->tree6
549#endif
550               )
551            {
552                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
553                                   "the \"ranges\" directive must be "
554                                   "the first directive inside \"geo\" block");
555                goto failed;
556            }
557
558            ctx->ranges = 1;
559
560            rv = NGX_CONF_OK;
561
562            goto done;
563        }
564    }
565
566    if (cf->args->nelts != 2) {
567        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
568                           "invalid number of the geo parameters");
569        goto failed;
570    }
571
572    if (ngx_strcmp(value[0].data, "include") == 0) {
573
574        rv = ngx_stream_geo_include(cf, ctx, &value[1]);
575
576        goto done;
577    }
578
579    if (ctx->ranges) {
580        rv = ngx_stream_geo_range(cf, ctx, value);
581
582    } else {
583        rv = ngx_stream_geo_cidr(cf, ctx, value);
584    }
585
586done:
587
588    ngx_reset_pool(cf->pool);
589
590    return rv;
591
592failed:
593
594    ngx_reset_pool(cf->pool);
595
596    return NGX_CONF_ERROR;
597}
598
599
600static char *
601ngx_stream_geo_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
602    ngx_str_t *value)
603{
604    u_char      *p, *last;
605    in_addr_t    start, end;
606    ngx_str_t   *net;
607    ngx_uint_t   del;
608
609    if (ngx_strcmp(value[0].data, "default") == 0) {
610
611        if (ctx->high.default_value) {
612            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
613                "duplicate default geo range value: \"%V\", old value: \"%v\"",
614                &value[1], ctx->high.default_value);
615        }
616
617        ctx->high.default_value = ngx_stream_geo_value(cf, ctx, &value[1]);
618        if (ctx->high.default_value == NULL) {
619            return NGX_CONF_ERROR;
620        }
621
622        return NGX_CONF_OK;
623    }
624
625    if (ctx->binary_include) {
626        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
627            "binary geo range base \"%s\" cannot be mixed with usual entries",
628            ctx->include_name.data);
629        return NGX_CONF_ERROR;
630    }
631
632    if (ctx->high.low == NULL) {
633        ctx->high.low = ngx_pcalloc(ctx->pool,
634                                    0x10000 * sizeof(ngx_stream_geo_range_t *));
635        if (ctx->high.low == NULL) {
636            return NGX_CONF_ERROR;
637        }
638    }
639
640    ctx->entries++;
641    ctx->outside_entries = 1;
642
643    if (ngx_strcmp(value[0].data, "delete") == 0) {
644        net = &value[1];
645        del = 1;
646
647    } else {
648        net = &value[0];
649        del = 0;
650    }
651
652    last = net->data + net->len;
653
654    p = ngx_strlchr(net->data, last, '-');
655
656    if (p == NULL) {
657        goto invalid;
658    }
659
660    start = ngx_inet_addr(net->data, p - net->data);
661
662    if (start == INADDR_NONE) {
663        goto invalid;
664    }
665
666    start = ntohl(start);
667
668    p++;
669
670    end = ngx_inet_addr(p, last - p);
671
672    if (end == INADDR_NONE) {
673        goto invalid;
674    }
675
676    end = ntohl(end);
677
678    if (start > end) {
679        goto invalid;
680    }
681
682    if (del) {
683        if (ngx_stream_geo_delete_range(cf, ctx, start, end)) {
684            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
685                               "no address range \"%V\" to delete", net);
686        }
687
688        return NGX_CONF_OK;
689    }
690
691    ctx->value = ngx_stream_geo_value(cf, ctx, &value[1]);
692
693    if (ctx->value == NULL) {
694        return NGX_CONF_ERROR;
695    }
696
697    ctx->net = net;
698
699    return ngx_stream_geo_add_range(cf, ctx, start, end);
700
701invalid:
702
703    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);
704
705    return NGX_CONF_ERROR;
706}
707
708
709/* the add procedure is optimized to add a growing up sequence */
710
711static char *
712ngx_stream_geo_add_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
713    in_addr_t start, in_addr_t end)
714{
715    in_addr_t                n;
716    ngx_uint_t               h, i, s, e;
717    ngx_array_t             *a;
718    ngx_stream_geo_range_t  *range;
719
720    for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
721
722        h = n >> 16;
723
724        if (n == start) {
725            s = n & 0xffff;
726        } else {
727            s = 0;
728        }
729
730        if ((n | 0xffff) > end) {
731            e = end & 0xffff;
732
733        } else {
734            e = 0xffff;
735        }
736
737        a = (ngx_array_t *) ctx->high.low[h];
738
739        if (a == NULL) {
740            a = ngx_array_create(ctx->temp_pool, 64,
741                                 sizeof(ngx_stream_geo_range_t));
742            if (a == NULL) {
743                return NGX_CONF_ERROR;
744            }
745
746            ctx->high.low[h] = (ngx_stream_geo_range_t *) a;
747        }
748
749        i = a->nelts;
750        range = a->elts;
751
752        while (i) {
753
754            i--;
755
756            if (e < (ngx_uint_t) range[i].start) {
757                continue;
758            }
759
760            if (s > (ngx_uint_t) range[i].end) {
761
762                /* add after the range */
763
764                range = ngx_array_push(a);
765                if (range == NULL) {
766                    return NGX_CONF_ERROR;
767                }
768
769                range = a->elts;
770
771                ngx_memmove(&range[i + 2], &range[i + 1],
772                           (a->nelts - 2 - i) * sizeof(ngx_stream_geo_range_t));
773
774                range[i + 1].start = (u_short) s;
775                range[i + 1].end = (u_short) e;
776                range[i + 1].value = ctx->value;
777
778                goto next;
779            }
780
781            if (s == (ngx_uint_t) range[i].start
782                && e == (ngx_uint_t) range[i].end)
783            {
784                ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
785                    "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
786                    ctx->net, ctx->value, range[i].value);
787
788                range[i].value = ctx->value;
789
790                goto next;
791            }
792
793            if (s > (ngx_uint_t) range[i].start
794                && e < (ngx_uint_t) range[i].end)
795            {
796                /* split the range and insert the new one */
797
798                range = ngx_array_push(a);
799                if (range == NULL) {
800                    return NGX_CONF_ERROR;
801                }
802
803                range = ngx_array_push(a);
804                if (range == NULL) {
805                    return NGX_CONF_ERROR;
806                }
807
808                range = a->elts;
809
810                ngx_memmove(&range[i + 3], &range[i + 1],
811                           (a->nelts - 3 - i) * sizeof(ngx_stream_geo_range_t));
812
813                range[i + 2].start = (u_short) (e + 1);
814                range[i + 2].end = range[i].end;
815                range[i + 2].value = range[i].value;
816
817                range[i + 1].start = (u_short) s;
818                range[i + 1].end = (u_short) e;
819                range[i + 1].value = ctx->value;
820
821                range[i].end = (u_short) (s - 1);
822
823                goto next;
824            }
825
826            if (s == (ngx_uint_t) range[i].start
827                && e < (ngx_uint_t) range[i].end)
828            {
829                /* shift the range start and insert the new range */
830
831                range = ngx_array_push(a);
832                if (range == NULL) {
833                    return NGX_CONF_ERROR;
834                }
835
836                range = a->elts;
837
838                ngx_memmove(&range[i + 1], &range[i],
839                           (a->nelts - 1 - i) * sizeof(ngx_stream_geo_range_t));
840
841                range[i + 1].start = (u_short) (e + 1);
842
843                range[i].start = (u_short) s;
844                range[i].end = (u_short) e;
845                range[i].value = ctx->value;
846
847                goto next;
848            }
849
850            if (s > (ngx_uint_t) range[i].start
851                && e == (ngx_uint_t) range[i].end)
852            {
853                /* shift the range end and insert the new range */
854
855                range = ngx_array_push(a);
856                if (range == NULL) {
857                    return NGX_CONF_ERROR;
858                }
859
860                range = a->elts;
861
862                ngx_memmove(&range[i + 2], &range[i + 1],
863                           (a->nelts - 2 - i) * sizeof(ngx_stream_geo_range_t));
864
865                range[i + 1].start = (u_short) s;
866                range[i + 1].end = (u_short) e;
867                range[i + 1].value = ctx->value;
868
869                range[i].end = (u_short) (s - 1);
870
871                goto next;
872            }
873
874            s = (ngx_uint_t) range[i].start;
875            e = (ngx_uint_t) range[i].end;
876
877            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
878                         "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"",
879                         ctx->net,
880                         h >> 8, h & 0xff, s >> 8, s & 0xff,
881                         h >> 8, h & 0xff, e >> 8, e & 0xff);
882
883            return NGX_CONF_ERROR;
884        }
885
886        /* add the first range */
887
888        range = ngx_array_push(a);
889        if (range == NULL) {
890            return NGX_CONF_ERROR;
891        }
892
893        range = a->elts;
894
895        ngx_memmove(&range[1], &range[0],
896                    (a->nelts - 1) * sizeof(ngx_stream_geo_range_t));
897
898        range[0].start = (u_short) s;
899        range[0].end = (u_short) e;
900        range[0].value = ctx->value;
901
902    next:
903
904        if (h == 0xffff) {
905            break;
906        }
907    }
908
909    return NGX_CONF_OK;
910}
911
912
913static ngx_uint_t
914ngx_stream_geo_delete_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
915    in_addr_t start, in_addr_t end)
916{
917    in_addr_t                n;
918    ngx_uint_t               h, i, s, e, warn;
919    ngx_array_t             *a;
920    ngx_stream_geo_range_t  *range;
921
922    warn = 0;
923
924    for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
925
926        h = n >> 16;
927
928        if (n == start) {
929            s = n & 0xffff;
930        } else {
931            s = 0;
932        }
933
934        if ((n | 0xffff) > end) {
935            e = end & 0xffff;
936
937        } else {
938            e = 0xffff;
939        }
940
941        a = (ngx_array_t *) ctx->high.low[h];
942
943        if (a == NULL || a->nelts == 0) {
944            warn = 1;
945            goto next;
946        }
947
948        range = a->elts;
949        for (i = 0; i < a->nelts; i++) {
950
951            if (s == (ngx_uint_t) range[i].start
952                && e == (ngx_uint_t) range[i].end)
953            {
954                ngx_memmove(&range[i], &range[i + 1],
955                           (a->nelts - 1 - i) * sizeof(ngx_stream_geo_range_t));
956
957                a->nelts--;
958
959                break;
960            }
961
962            if (i == a->nelts - 1) {
963                warn = 1;
964            }
965        }
966
967    next:
968
969        if (h == 0xffff) {
970            break;
971        }
972    }
973
974    return warn;
975}
976
977
978static char *
979ngx_stream_geo_cidr(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
980    ngx_str_t *value)
981{
982    char        *rv;
983    ngx_int_t    rc, del;
984    ngx_str_t   *net;
985    ngx_cidr_t   cidr;
986
987    if (ctx->tree == NULL) {
988        ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
989        if (ctx->tree == NULL) {
990            return NGX_CONF_ERROR;
991        }
992    }
993
994#if (NGX_HAVE_INET6)
995    if (ctx->tree6 == NULL) {
996        ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1);
997        if (ctx->tree6 == NULL) {
998            return NGX_CONF_ERROR;
999        }
1000    }
1001#endif
1002
1003    if (ngx_strcmp(value[0].data, "default") == 0) {
1004        cidr.family = AF_INET;
1005        cidr.u.in.addr = 0;
1006        cidr.u.in.mask = 0;
1007
1008        rv = ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
1009
1010        if (rv != NGX_CONF_OK) {
1011            return rv;
1012        }
1013
1014#if (NGX_HAVE_INET6)
1015        cidr.family = AF_INET6;
1016        ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t));
1017
1018        rv = ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
1019
1020        if (rv != NGX_CONF_OK) {
1021            return rv;
1022        }
1023#endif
1024
1025        return NGX_CONF_OK;
1026    }
1027
1028    if (ngx_strcmp(value[0].data, "delete") == 0) {
1029        net = &value[1];
1030        del = 1;
1031
1032    } else {
1033        net = &value[0];
1034        del = 0;
1035    }
1036
1037    if (ngx_stream_geo_cidr_value(cf, net, &cidr) != NGX_OK) {
1038        return NGX_CONF_ERROR;
1039    }
1040
1041    if (cidr.family == AF_INET) {
1042        cidr.u.in.addr = ntohl(cidr.u.in.addr);
1043        cidr.u.in.mask = ntohl(cidr.u.in.mask);
1044    }
1045
1046    if (del) {
1047        switch (cidr.family) {
1048
1049#if (NGX_HAVE_INET6)
1050        case AF_INET6:
1051            rc = ngx_radix128tree_delete(ctx->tree6,
1052                                         cidr.u.in6.addr.s6_addr,
1053                                         cidr.u.in6.mask.s6_addr);
1054            break;
1055#endif
1056
1057        default: /* AF_INET */
1058            rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
1059                                        cidr.u.in.mask);
1060            break;
1061        }
1062
1063        if (rc != NGX_OK) {
1064            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
1065                               "no network \"%V\" to delete", net);
1066        }
1067
1068        return NGX_CONF_OK;
1069    }
1070
1071    return ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], net);
1072}
1073
1074
1075static char *
1076ngx_stream_geo_cidr_add(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
1077    ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net)
1078{
1079    ngx_int_t                     rc;
1080    ngx_stream_variable_value_t  *val, *old;
1081
1082    val = ngx_stream_geo_value(cf, ctx, value);
1083
1084    if (val == NULL) {
1085        return NGX_CONF_ERROR;
1086    }
1087
1088    switch (cidr->family) {
1089
1090#if (NGX_HAVE_INET6)
1091    case AF_INET6:
1092        rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
1093                                     cidr->u.in6.mask.s6_addr,
1094                                     (uintptr_t) val);
1095
1096        if (rc == NGX_OK) {
1097            return NGX_CONF_OK;
1098        }
1099
1100        if (rc == NGX_ERROR) {
1101            return NGX_CONF_ERROR;
1102        }
1103
1104        /* rc == NGX_BUSY */
1105
1106        old = (ngx_stream_variable_value_t *)
1107                   ngx_radix128tree_find(ctx->tree6,
1108                                         cidr->u.in6.addr.s6_addr);
1109
1110        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
1111              "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
1112              net, val, old);
1113
1114        rc = ngx_radix128tree_delete(ctx->tree6,
1115                                     cidr->u.in6.addr.s6_addr,
1116                                     cidr->u.in6.mask.s6_addr);
1117
1118        if (rc == NGX_ERROR) {
1119            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
1120            return NGX_CONF_ERROR;
1121        }
1122
1123        rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
1124                                     cidr->u.in6.mask.s6_addr,
1125                                     (uintptr_t) val);
1126
1127        break;
1128#endif
1129
1130    default: /* AF_INET */
1131        rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
1132                                    cidr->u.in.mask, (uintptr_t) val);
1133
1134        if (rc == NGX_OK) {
1135            return NGX_CONF_OK;
1136        }
1137
1138        if (rc == NGX_ERROR) {
1139            return NGX_CONF_ERROR;
1140        }
1141
1142        /* rc == NGX_BUSY */
1143
1144        old = (ngx_stream_variable_value_t *)
1145                   ngx_radix32tree_find(ctx->tree, cidr->u.in.addr);
1146
1147        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
1148              "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
1149              net, val, old);
1150
1151        rc = ngx_radix32tree_delete(ctx->tree,
1152                                    cidr->u.in.addr, cidr->u.in.mask);
1153
1154        if (rc == NGX_ERROR) {
1155            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
1156            return NGX_CONF_ERROR;
1157        }
1158
1159        rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
1160                                    cidr->u.in.mask, (uintptr_t) val);
1161
1162        break;
1163    }
1164
1165    if (rc == NGX_OK) {
1166        return NGX_CONF_OK;
1167    }
1168
1169    return NGX_CONF_ERROR;
1170}
1171
1172
1173static ngx_stream_variable_value_t *
1174ngx_stream_geo_value(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
1175    ngx_str_t *value)
1176{
1177    uint32_t                               hash;
1178    ngx_stream_variable_value_t           *val;
1179    ngx_stream_geo_variable_value_node_t  *gvvn;
1180
1181    hash = ngx_crc32_long(value->data, value->len);
1182
1183    gvvn = (ngx_stream_geo_variable_value_node_t *)
1184               ngx_str_rbtree_lookup(&ctx->rbtree, value, hash);
1185
1186    if (gvvn) {
1187        return gvvn->value;
1188    }
1189
1190    val = ngx_palloc(ctx->pool, sizeof(ngx_stream_variable_value_t));
1191    if (val == NULL) {
1192        return NULL;
1193    }
1194
1195    val->len = value->len;
1196    val->data = ngx_pstrdup(ctx->pool, value);
1197    if (val->data == NULL) {
1198        return NULL;
1199    }
1200
1201    val->valid = 1;
1202    val->no_cacheable = 0;
1203    val->not_found = 0;
1204
1205    gvvn = ngx_palloc(ctx->temp_pool,
1206                      sizeof(ngx_stream_geo_variable_value_node_t));
1207    if (gvvn == NULL) {
1208        return NULL;
1209    }
1210
1211    gvvn->sn.node.key = hash;
1212    gvvn->sn.str.len = val->len;
1213    gvvn->sn.str.data = val->data;
1214    gvvn->value = val;
1215    gvvn->offset = 0;
1216
1217    ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node);
1218
1219    ctx->data_size += ngx_align(sizeof(ngx_stream_variable_value_t)
1220                                + value->len, sizeof(void *));
1221
1222    return val;
1223}
1224
1225
1226static ngx_int_t
1227ngx_stream_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr)
1228{
1229    ngx_int_t  rc;
1230
1231    if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
1232        cidr->family = AF_INET;
1233        cidr->u.in.addr = 0xffffffff;
1234        cidr->u.in.mask = 0xffffffff;
1235
1236        return NGX_OK;
1237    }
1238
1239    rc = ngx_ptocidr(net, cidr);
1240
1241    if (rc == NGX_ERROR) {
1242        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net);
1243        return NGX_ERROR;
1244    }
1245
1246    if (rc == NGX_DONE) {
1247        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
1248                           "low address bits of %V are meaningless", net);
1249    }
1250
1251    return NGX_OK;
1252}
1253
1254
1255static char *
1256ngx_stream_geo_include(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
1257    ngx_str_t *name)
1258{
1259    char       *rv;
1260    ngx_str_t   file;
1261
1262    file.len = name->len + 4;
1263    file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5);
1264    if (file.data == NULL) {
1265        return NGX_CONF_ERROR;
1266    }
1267
1268    ngx_sprintf(file.data, "%V.bin%Z", name);
1269
1270    if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
1271        return NGX_CONF_ERROR;
1272    }
1273
1274    if (ctx->ranges) {
1275        ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
1276
1277        switch (ngx_stream_geo_include_binary_base(cf, ctx, &file)) {
1278        case NGX_OK:
1279            return NGX_CONF_OK;
1280        case NGX_ERROR:
1281            return NGX_CONF_ERROR;
1282        default:
1283            break;
1284        }
1285    }
1286
1287    file.len -= 4;
1288    file.data[file.len] = '\0';
1289
1290    ctx->include_name = file;
1291
1292    if (ctx->outside_entries) {
1293        ctx->allow_binary_include = 0;
1294    }
1295
1296    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
1297
1298    rv = ngx_conf_parse(cf, &file);
1299
1300    ctx->includes++;
1301    ctx->outside_entries = 0;
1302
1303    return rv;
1304}
1305
1306
1307static ngx_int_t
1308ngx_stream_geo_include_binary_base(ngx_conf_t *cf,
1309    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name)
1310{
1311    u_char                       *base, ch;
1312    time_t                        mtime;
1313    size_t                        size, len;
1314    ssize_t                       n;
1315    uint32_t                      crc32;
1316    ngx_err_t                     err;
1317    ngx_int_t                     rc;
1318    ngx_uint_t                    i;
1319    ngx_file_t                    file;
1320    ngx_file_info_t               fi;
1321    ngx_stream_geo_range_t       *range, **ranges;
1322    ngx_stream_geo_header_t      *header;
1323    ngx_stream_variable_value_t  *vv;
1324
1325    ngx_memzero(&file, sizeof(ngx_file_t));
1326    file.name = *name;
1327    file.log = cf->log;
1328
1329    file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, 0, 0);
1330    if (file.fd == NGX_INVALID_FILE) {
1331        err = ngx_errno;
1332        if (err != NGX_ENOENT) {
1333            ngx_conf_log_error(NGX_LOG_CRIT, cf, err,
1334                               ngx_open_file_n " \"%s\" failed", name->data);
1335        }
1336        return NGX_DECLINED;
1337    }
1338
1339    if (ctx->outside_entries) {
1340        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1341            "binary geo range base \"%s\" cannot be mixed with usual entries",
1342            name->data);
1343        rc = NGX_ERROR;
1344        goto done;
1345    }
1346
1347    if (ctx->binary_include) {
1348        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1349            "second binary geo range base \"%s\" cannot be mixed with \"%s\"",
1350            name->data, ctx->include_name.data);
1351        rc = NGX_ERROR;
1352        goto done;
1353    }
1354
1355    if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
1356        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
1357                           ngx_fd_info_n " \"%s\" failed", name->data);
1358        goto failed;
1359    }
1360
1361    size = (size_t) ngx_file_size(&fi);
1362    mtime = ngx_file_mtime(&fi);
1363
1364    ch = name->data[name->len - 4];
1365    name->data[name->len - 4] = '\0';
1366
1367    if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
1368        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
1369                           ngx_file_info_n " \"%s\" failed", name->data);
1370        goto failed;
1371    }
1372
1373    name->data[name->len - 4] = ch;
1374
1375    if (mtime < ngx_file_mtime(&fi)) {
1376        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
1377                           "stale binary geo range base \"%s\"", name->data);
1378        goto failed;
1379    }
1380
1381    base = ngx_palloc(ctx->pool, size);
1382    if (base == NULL) {
1383        goto failed;
1384    }
1385
1386    n = ngx_read_file(&file, base, size, 0);
1387
1388    if (n == NGX_ERROR) {
1389        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
1390                           ngx_read_file_n " \"%s\" failed", name->data);
1391        goto failed;
1392    }
1393
1394    if ((size_t) n != size) {
1395        ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
1396            ngx_read_file_n " \"%s\" returned only %z bytes instead of %z",
1397            name->data, n, size);
1398        goto failed;
1399    }
1400
1401    header = (ngx_stream_geo_header_t *) base;
1402
1403    if (size < 16 || ngx_memcmp(&ngx_stream_geo_header, header, 12) != 0) {
1404        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
1405             "incompatible binary geo range base \"%s\"", name->data);
1406        goto failed;
1407    }
1408
1409    ngx_crc32_init(crc32);
1410
1411    vv = (ngx_stream_variable_value_t *)
1412            (base + sizeof(ngx_stream_geo_header_t));
1413
1414    while (vv->data) {
1415        len = ngx_align(sizeof(ngx_stream_variable_value_t) + vv->len,
1416                        sizeof(void *));
1417        ngx_crc32_update(&crc32, (u_char *) vv, len);
1418        vv->data += (size_t) base;
1419        vv = (ngx_stream_variable_value_t *) ((u_char *) vv + len);
1420    }
1421    ngx_crc32_update(&crc32, (u_char *) vv,
1422                     sizeof(ngx_stream_variable_value_t));
1423    vv++;
1424
1425    ranges = (ngx_stream_geo_range_t **) vv;
1426
1427    for (i = 0; i < 0x10000; i++) {
1428        ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *));
1429        if (ranges[i]) {
1430            ranges[i] = (ngx_stream_geo_range_t *)
1431                            ((u_char *) ranges[i] + (size_t) base);
1432        }
1433    }
1434
1435    range = (ngx_stream_geo_range_t *) &ranges[0x10000];
1436
1437    while ((u_char *) range < base + size) {
1438        while (range->value) {
1439            ngx_crc32_update(&crc32, (u_char *) range,
1440                             sizeof(ngx_stream_geo_range_t));
1441            range->value = (ngx_stream_variable_value_t *)
1442                               ((u_char *) range->value + (size_t) base);
1443            range++;
1444        }
1445        ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *));
1446        range = (ngx_stream_geo_range_t *) ((u_char *) range + sizeof(void *));
1447    }
1448
1449    ngx_crc32_final(crc32);
1450
1451    if (crc32 != header->crc32) {
1452        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
1453                  "CRC32 mismatch in binary geo range base \"%s\"", name->data);
1454        goto failed;
1455    }
1456
1457    ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0,
1458                       "using binary geo range base \"%s\"", name->data);
1459
1460    ctx->include_name = *name;
1461    ctx->binary_include = 1;
1462    ctx->high.low = ranges;
1463    rc = NGX_OK;
1464
1465    goto done;
1466
1467failed:
1468
1469    rc = NGX_DECLINED;
1470
1471done:
1472
1473    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
1474        ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
1475                      ngx_close_file_n " \"%s\" failed", name->data);
1476    }
1477
1478    return rc;
1479}
1480
1481
1482static void
1483ngx_stream_geo_create_binary_base(ngx_stream_geo_conf_ctx_t *ctx)
1484{
1485    u_char                                *p;
1486    uint32_t                               hash;
1487    ngx_str_t                              s;
1488    ngx_uint_t                             i;
1489    ngx_file_mapping_t                     fm;
1490    ngx_stream_geo_range_t                *r, *range, **ranges;
1491    ngx_stream_geo_header_t               *header;
1492    ngx_stream_geo_variable_value_node_t  *gvvn;
1493
1494    fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5);
1495    if (fm.name == NULL) {
1496        return;
1497    }
1498
1499    ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name);
1500
1501    fm.size = ctx->data_size;
1502    fm.log = ctx->pool->log;
1503
1504    ngx_log_error(NGX_LOG_NOTICE, fm.log, 0,
1505                  "creating binary geo range base \"%s\"", fm.name);
1506
1507    if (ngx_create_file_mapping(&fm) != NGX_OK) {
1508        return;
1509    }
1510
1511    p = ngx_cpymem(fm.addr, &ngx_stream_geo_header,
1512                   sizeof(ngx_stream_geo_header_t));
1513
1514    p = ngx_stream_geo_copy_values(fm.addr, p, ctx->rbtree.root,
1515                                   ctx->rbtree.sentinel);
1516
1517    p += sizeof(ngx_stream_variable_value_t);
1518
1519    ranges = (ngx_stream_geo_range_t **) p;
1520
1521    p += 0x10000 * sizeof(ngx_stream_geo_range_t *);
1522
1523    for (i = 0; i < 0x10000; i++) {
1524        r = ctx->high.low[i];
1525        if (r == NULL) {
1526            continue;
1527        }
1528
1529        range = (ngx_stream_geo_range_t *) p;
1530        ranges[i] = (ngx_stream_geo_range_t *) (p - (u_char *) fm.addr);
1531
1532        do {
1533            s.len = r->value->len;
1534            s.data = r->value->data;
1535            hash = ngx_crc32_long(s.data, s.len);
1536            gvvn = (ngx_stream_geo_variable_value_node_t *)
1537                        ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash);
1538
1539            range->value = (ngx_stream_variable_value_t *) gvvn->offset;
1540            range->start = r->start;
1541            range->end = r->end;
1542            range++;
1543
1544        } while ((++r)->value);
1545
1546        range->value = NULL;
1547
1548        p = (u_char *) range + sizeof(void *);
1549    }
1550
1551    header = fm.addr;
1552    header->crc32 = ngx_crc32_long((u_char *) fm.addr
1553                                       + sizeof(ngx_stream_geo_header_t),
1554                                   fm.size - sizeof(ngx_stream_geo_header_t));
1555
1556    ngx_close_file_mapping(&fm);
1557}
1558
1559
1560static u_char *
1561ngx_stream_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node,
1562    ngx_rbtree_node_t *sentinel)
1563{
1564    ngx_stream_variable_value_t           *vv;
1565    ngx_stream_geo_variable_value_node_t  *gvvn;
1566
1567    if (node == sentinel) {
1568        return p;
1569    }
1570
1571    gvvn = (ngx_stream_geo_variable_value_node_t *) node;
1572    gvvn->offset = p - base;
1573
1574    vv = (ngx_stream_variable_value_t *) p;
1575    *vv = *gvvn->value;
1576    p += sizeof(ngx_stream_variable_value_t);
1577    vv->data = (u_char *) (p - base);
1578
1579    p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len);
1580
1581    p = ngx_align_ptr(p, sizeof(void *));
1582
1583    p = ngx_stream_geo_copy_values(base, p, node->left, sentinel);
1584
1585    return ngx_stream_geo_copy_values(base, p, node->right, sentinel);
1586}
1587