upstream nginx-0.7.39
[nginx.git] / nginx / src / http / modules / ngx_http_geo_module.c
1
2 /*
3  * Copyright (C) Igor Sysoev
4  */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
10
11
12 typedef struct {
13     u_short                          start;
14     u_short                          end;
15     ngx_http_variable_value_t       *value;
16 } ngx_http_geo_range_t;
17
18
19 typedef struct {
20     ngx_http_geo_range_t            *ranges;
21     ngx_uint_t                       n;
22 } ngx_http_geo_low_ranges_t;
23
24
25 typedef struct {
26     ngx_http_geo_low_ranges_t        low[0x10000];
27     ngx_http_variable_value_t       *default_value;
28 } ngx_http_geo_high_ranges_t;
29
30
31 typedef struct {
32     ngx_http_variable_value_t       *value;
33     ngx_str_t                       *net;
34     ngx_http_geo_high_ranges_t      *high;
35     ngx_radix_tree_t                *tree;
36     ngx_rbtree_t                     rbtree;
37     ngx_rbtree_node_t                sentinel;
38     ngx_pool_t                      *pool;
39     ngx_pool_t                      *temp_pool;
40 } ngx_http_geo_conf_ctx_t;
41
42
43 typedef struct {
44     union {
45         ngx_radix_tree_t            *tree;
46         ngx_http_geo_high_ranges_t  *high;
47     } u;
48
49     ngx_int_t                        index;
50 } ngx_http_geo_ctx_t;
51
52
53 static in_addr_t ngx_http_geo_addr(ngx_http_request_t *r,
54     ngx_http_geo_ctx_t *ctx);
55 static char *ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
56 static char *ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
57 static char *ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
58     ngx_str_t *value);
59 static char *ngx_http_geo_add_range(ngx_conf_t *cf,
60     ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
61 static ngx_uint_t ngx_http_geo_delete_range(ngx_conf_t *cf,
62     ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
63 static char *ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
64     ngx_str_t *value);
65 static ngx_http_variable_value_t *ngx_http_geo_value(ngx_conf_t *cf,
66     ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value);
67
68
69 static ngx_command_t  ngx_http_geo_commands[] = {
70
71     { ngx_string("geo"),
72       NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
73       ngx_http_geo_block,
74       NGX_HTTP_MAIN_CONF_OFFSET,
75       0,
76       NULL },
77
78       ngx_null_command
79 };
80
81
82 static ngx_http_module_t  ngx_http_geo_module_ctx = {
83     NULL,                                  /* preconfiguration */
84     NULL,                                  /* postconfiguration */
85
86     NULL,                                  /* create main configuration */
87     NULL,                                  /* init main configuration */
88
89     NULL,                                  /* create server configuration */
90     NULL,                                  /* merge server configuration */
91
92     NULL,                                  /* create location configuration */
93     NULL                                   /* merge location configuration */
94 };
95
96
97 ngx_module_t  ngx_http_geo_module = {
98     NGX_MODULE_V1,
99     &ngx_http_geo_module_ctx,              /* module context */
100     ngx_http_geo_commands,                 /* module directives */
101     NGX_HTTP_MODULE,                       /* module type */
102     NULL,                                  /* init master */
103     NULL,                                  /* init module */
104     NULL,                                  /* init process */
105     NULL,                                  /* init thread */
106     NULL,                                  /* exit thread */
107     NULL,                                  /* exit process */
108     NULL,                                  /* exit master */
109     NGX_MODULE_V1_PADDING
110 };
111
112
113 /* AF_INET only */
114
115 static ngx_int_t
116 ngx_http_geo_cidr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
117     uintptr_t data)
118 {
119     ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
120
121     ngx_http_variable_value_t  *vv;
122
123     vv = (ngx_http_variable_value_t *)
124               ngx_radix32tree_find(ctx->u.tree, ngx_http_geo_addr(r, ctx));
125
126     *v = *vv;
127
128     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
129                    "http geo: %v", v);
130
131     return NGX_OK;
132 }
133
134
135 static ngx_int_t
136 ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
137     uintptr_t data)
138 {
139     ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
140
141     in_addr_t              addr;
142     ngx_uint_t             i, n;
143     ngx_http_geo_range_t  *range;
144
145     *v = *ctx->u.high->default_value;
146
147     addr = ngx_http_geo_addr(r, ctx);
148
149     range = ctx->u.high->low[addr >> 16].ranges;
150
151     n = addr & 0xffff;
152
153     for (i = 0; i < ctx->u.high->low[addr >> 16].n; i++) {
154         if (n >= (ngx_uint_t) range[i].start
155             && n <= (ngx_uint_t) range[i].end)
156         {
157             *v = *range[i].value;
158         }
159     }
160
161     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
162                    "http geo: %v", v);
163
164     return NGX_OK;
165 }
166
167
168 static in_addr_t
169 ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx)
170 {
171     struct sockaddr_in         *sin;
172     ngx_http_variable_value_t  *v;
173
174     if (ctx->index == -1) {
175         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
176                        "http geo started: %V", &r->connection->addr_text);
177
178         if (r->connection->sockaddr->sa_family != AF_INET) {
179             return 0;
180         }
181
182         sin = (struct sockaddr_in *) r->connection->sockaddr;
183         return ntohl(sin->sin_addr.s_addr);
184     }
185
186     v = ngx_http_get_flushed_variable(r, ctx->index);
187
188     if (v == NULL || v->not_found) {
189         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
190                        "http geo not found");
191
192         return 0;
193     }
194
195     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
196                    "http geo started: %v", v);
197
198     return ntohl(ngx_inet_addr(v->data, v->len));
199 }
200
201
202 static char *
203 ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
204 {
205     char                     *rv;
206     size_t                    len;
207     ngx_str_t                *value, name;
208     ngx_uint_t                i;
209     ngx_conf_t                save;
210     ngx_pool_t               *pool;
211     ngx_array_t              *a;
212     ngx_http_variable_t      *var;
213     ngx_http_geo_ctx_t       *geo;
214     ngx_http_geo_conf_ctx_t   ctx;
215
216     value = cf->args->elts;
217
218     geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t));
219     if (geo == NULL) {
220         return NGX_CONF_ERROR;
221     }
222
223     name = value[1];
224     name.len--;
225     name.data++;
226
227     if (cf->args->nelts == 3) {
228
229         geo->index = ngx_http_get_variable_index(cf, &name);
230         if (geo->index == NGX_ERROR) {
231             return NGX_CONF_ERROR;
232         }
233
234         name = value[2];
235         name.len--;
236         name.data++;
237
238     } else {
239         geo->index = -1;
240     }
241
242     var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
243     if (var == NULL) {
244         return NGX_CONF_ERROR;
245     }
246
247     pool = ngx_create_pool(16384, cf->log);
248     if (pool == NULL) {
249         return NGX_CONF_ERROR;
250     }
251
252     ctx.temp_pool = ngx_create_pool(16384, cf->log);
253     if (ctx.temp_pool == NULL) {
254         return NGX_CONF_ERROR;
255     }
256
257     ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel,
258                     ngx_http_variable_value_rbtree_insert);
259
260     ctx.high = NULL;
261     ctx.tree = NULL;
262     ctx.pool = cf->pool;
263
264     save = *cf;
265     cf->pool = pool;
266     cf->ctx = &ctx;
267     cf->handler = ngx_http_geo;
268     cf->handler_conf = conf;
269
270     rv = ngx_conf_parse(cf, NULL);
271
272     *cf = save;
273
274     if (ctx.high) {
275
276         for (i = 0; i < 0x10000; i++) {
277             a = (ngx_array_t *) ctx.high->low[i].ranges;
278
279             if (a == NULL || a->nelts == 0) {
280                 continue;
281             }
282
283             ctx.high->low[i].n = a->nelts;
284
285             len = a->nelts * sizeof(ngx_http_geo_range_t);
286
287             ctx.high->low[i].ranges = ngx_palloc(cf->pool, len);
288             if (ctx.high->low[i].ranges == NULL ){
289                 return NGX_CONF_ERROR;
290             }
291
292             ngx_memcpy(ctx.high->low[i].ranges, a->elts, len);
293         }
294
295         geo->u.high = ctx.high;
296
297         var->get_handler = ngx_http_geo_range_variable;
298         var->data = (uintptr_t) geo;
299
300         ngx_destroy_pool(ctx.temp_pool);
301         ngx_destroy_pool(pool);
302
303         if (ctx.high->default_value == NULL) {
304             ctx.high->default_value = &ngx_http_variable_null_value;
305         }
306
307     } else {
308         if (ctx.tree == NULL) {
309             ctx.tree = ngx_radix_tree_create(cf->pool, -1);
310             if (ctx.tree == NULL) {
311                 return NGX_CONF_ERROR;
312             }
313         }
314
315         geo->u.tree = ctx.tree;
316
317         var->get_handler = ngx_http_geo_cidr_variable;
318         var->data = (uintptr_t) geo;
319
320         ngx_destroy_pool(ctx.temp_pool);
321         ngx_destroy_pool(pool);
322
323         if (ngx_radix32tree_find(ctx.tree, 0) != NGX_RADIX_NO_VALUE) {
324             return rv;
325         }
326
327         if (ngx_radix32tree_insert(ctx.tree, 0, 0,
328                                    (uintptr_t) &ngx_http_variable_null_value)
329             == NGX_ERROR)
330         {
331             return NGX_CONF_ERROR;
332         }
333     }
334
335     return rv;
336 }
337
338
339 static char *
340 ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
341 {
342     char                     *rv;
343     ngx_str_t                *value, file;
344     ngx_http_geo_conf_ctx_t  *ctx;
345
346     ctx = cf->ctx;
347
348     value = cf->args->elts;
349
350     if (cf->args->nelts == 1) {
351
352         if (ngx_strcmp(value[0].data, "ranges") == 0) {
353
354             if (ctx->tree) {
355                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
356                                    "the \"ranges\" directive must be "
357                                    "the first directive inside \"geo\" block");
358                 goto failed;
359             }
360
361             ctx->high = ngx_pcalloc(ctx->pool,
362                                     sizeof(ngx_http_geo_high_ranges_t));
363             if (ctx->high == NULL) {
364                 goto failed;
365             }
366
367             rv = NGX_CONF_OK;
368
369             goto done;
370         }
371     }
372
373     if (cf->args->nelts != 2) {
374         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
375                            "invalid number of the geo parameters");
376         goto failed;
377     }
378
379     if (ngx_strcmp(value[0].data, "include") == 0) {
380
381         file.len = value[1].len++;
382
383         file.data = ngx_pstrdup(ctx->temp_pool, &value[1]);
384         if (file.data == NULL) {
385             goto failed;
386         }
387
388         if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK){
389             goto failed;
390         }
391
392         ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
393
394         rv = ngx_conf_parse(cf, &file);
395
396         goto done;
397     }
398
399     if (ctx->high) {
400         rv = ngx_http_geo_range(cf, ctx, value);
401
402     } else {
403         rv = ngx_http_geo_cidr(cf, ctx, value);
404     }
405
406 done:
407
408     ngx_reset_pool(cf->pool);
409
410     return rv;
411
412 failed:
413
414     ngx_reset_pool(cf->pool);
415
416     return NGX_CONF_ERROR;
417 }
418
419
420 static char *
421 ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
422     ngx_str_t *value)
423 {
424     u_char                     *p, *last;
425     in_addr_t                   start, end;
426     ngx_str_t                  *net;
427     ngx_uint_t                  del;
428     ngx_http_variable_value_t  *old;
429
430     if (ngx_strcmp(value[0].data, "default") == 0) {
431
432         old = ctx->high->default_value;
433
434         ctx->high->default_value = ngx_http_geo_value(cf, ctx, &value[1]);
435         if (ctx->high->default_value == NULL) {
436             return NGX_CONF_ERROR;
437         }
438
439         if (old) {
440             ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
441                     "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
442                     &value[0], ctx->high->default_value, old);
443         }
444
445         return NGX_CONF_OK;
446     }
447
448     if (ngx_strcmp(value[0].data, "delete") == 0) {
449         net = &value[1];
450         del = 1;
451
452     } else {
453         net = &value[0];
454         del = 0;
455     }
456
457     last = net->data + net->len;
458
459     p = ngx_strlchr(net->data, last, '-');
460
461     if (p == NULL) {
462         goto invalid;
463     }
464
465     start = ngx_inet_addr(net->data, p - net->data);
466
467     if (start == INADDR_NONE) {
468         goto invalid;
469     }
470
471     start = ntohl(start);
472
473     p++;
474
475     end = ngx_inet_addr(p, last - p);
476
477     if (end == INADDR_NONE) {
478         goto invalid;
479     }
480
481     end = ntohl(end);
482
483     if (start > end) {
484         goto invalid;
485     }
486
487     if (del) {
488         if (ngx_http_geo_delete_range(cf, ctx, start, end)) {
489             ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
490                                "no address range \"%V\" to delete", net);
491         }
492
493         return NGX_CONF_OK;
494     }
495
496     ctx->value = ngx_http_geo_value(cf, ctx, &value[1]);
497
498     if (ctx->value == NULL) {
499         return NGX_CONF_ERROR;
500     }
501
502     ctx->net = net;
503
504     return ngx_http_geo_add_range(cf, ctx, start, end);
505
506 invalid:
507
508     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);
509
510     return NGX_CONF_ERROR;
511 }
512
513
514 /* the add procedure is optimized to add a growing up sequence */
515
516 static char *
517 ngx_http_geo_add_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
518     in_addr_t start, in_addr_t end)
519 {
520     in_addr_t              n;
521     ngx_uint_t             h, i, s, e;
522     ngx_array_t           *a;
523     ngx_http_geo_range_t  *range;
524
525     for (n = start; n <= end; n += 0x10000) {
526
527         h = n >> 16;
528
529         if (n == start) {
530             s = n & 0xffff;
531         } else {
532             s = 0;
533         }
534
535         if ((n | 0xffff) > end) {
536             e = end & 0xffff;
537
538         } else {
539             e = 0xffff;
540         }
541
542         a = (ngx_array_t *) ctx->high->low[h].ranges;
543
544         if (a == NULL) {
545             a = ngx_array_create(ctx->temp_pool, 64,
546                                  sizeof(ngx_http_geo_range_t));
547             if (a == NULL) {
548                 return NGX_CONF_ERROR;
549             }
550
551             ctx->high->low[h].ranges = (ngx_http_geo_range_t *) a;
552         }
553
554         i = a->nelts;
555         range = a->elts;
556
557         while (i) {
558
559             i--;
560
561             if (e < (ngx_uint_t) range[i].start) {
562                 continue;
563             }
564
565             if (s > (ngx_uint_t) range[i].end) {
566
567                 /* add after the range */
568
569                 range = ngx_array_push(a);
570                 if (range == NULL) {
571                     return NGX_CONF_ERROR;
572                 }
573
574                 range = a->elts;
575
576                 ngx_memcpy(&range[i + 2], &range[i + 1],
577                            (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
578
579                 range[i + 1].start = (u_short) s;
580                 range[i + 1].end = (u_short) e;
581                 range[i + 1].value = ctx->value;
582
583                 goto next;
584             }
585
586             if (s == (ngx_uint_t) range[i].start
587                 && e == (ngx_uint_t) range[i].end)
588             {
589                 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
590                     "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
591                     ctx->net, ctx->value, range[i].value);
592
593                 range[i].value = ctx->value;
594
595                 goto next;
596             }
597
598             if (s > (ngx_uint_t) range[i].start
599                 && e < (ngx_uint_t) range[i].end)
600             {
601                 /* split the range and insert the new one */
602
603                 range = ngx_array_push(a);
604                 if (range == NULL) {
605                     return NGX_CONF_ERROR;
606                 }
607
608                 range = ngx_array_push(a);
609                 if (range == NULL) {
610                     return NGX_CONF_ERROR;
611                 }
612
613                 range = a->elts;
614
615                 ngx_memcpy(&range[i + 3], &range[i + 1],
616                            (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t));
617
618                 range[i + 2].start = (u_short) (e + 1);
619                 range[i + 2].end = range[i].end;
620                 range[i + 2].value = range[i].value;
621
622                 range[i + 1].start = (u_short) s;
623                 range[i + 1].end = (u_short) e;
624                 range[i + 1].value = ctx->value;
625
626                 range[i].end = (u_short) (s - 1);
627
628                 goto next;
629             }
630
631             if (s == (ngx_uint_t) range[i].start
632                 && e < (ngx_uint_t) range[i].end)
633             {
634                 /* shift the range start and insert the new range */
635
636                 range = ngx_array_push(a);
637                 if (range == NULL) {
638                     return NGX_CONF_ERROR;
639                 }
640
641                 range = a->elts;
642
643                 ngx_memcpy(&range[i + 1], &range[i],
644                            (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
645
646                 range[i + 1].start = (u_short) (e + 1);
647
648                 range[i].start = (u_short) s;
649                 range[i].end = (u_short) e;
650                 range[i].value = ctx->value;
651
652                 goto next;
653             }
654
655             if (s > (ngx_uint_t) range[i].start
656                 && e == (ngx_uint_t) range[i].end)
657             {
658                 /* shift the range end and insert the new range */
659
660                 range = ngx_array_push(a);
661                 if (range == NULL) {
662                     return NGX_CONF_ERROR;
663                 }
664
665                 range = a->elts;
666
667                 ngx_memcpy(&range[i + 2], &range[i + 1],
668                            (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
669
670                 range[i + 1].start = (u_short) s;
671                 range[i + 1].end = (u_short) e;
672                 range[i + 1].value = ctx->value;
673
674                 range[i].end = (u_short) (s - 1);
675
676                 goto next;
677             }
678
679             s = (ngx_uint_t) range[i].start;
680             e = (ngx_uint_t) range[i].end;
681
682             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
683                          "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"",
684                          ctx->net,
685                          h >> 8, h & 0xff, s >> 8, s & 0xff,
686                          h >> 8, h & 0xff, e >> 8, e & 0xff);
687
688             return NGX_CONF_ERROR;
689         }
690
691         /* add the first range */
692
693         range = ngx_array_push(a);
694         if (range == NULL) {
695             return NGX_CONF_ERROR;
696         }
697
698         range->start = (u_short) s;
699         range->end = (u_short) e;
700         range->value = ctx->value;
701
702     next:
703
704         continue;
705     }
706
707     return NGX_CONF_OK;
708 }
709
710
711 static ngx_uint_t
712 ngx_http_geo_delete_range(ngx_conf_t *cf, ngx_http_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, warn;
717     ngx_array_t           *a;
718     ngx_http_geo_range_t  *range;
719
720     warn = 0;
721
722     for (n = start; n <= end; n += 0x10000) {
723
724         h = n >> 16;
725
726         if (n == start) {
727             s = n & 0xffff;
728         } else {
729             s = 0;
730         }
731
732         if ((n | 0xffff) > end) {
733             e = end & 0xffff;
734
735         } else {
736             e = 0xffff;
737         }
738
739         a = (ngx_array_t *) ctx->high->low[h].ranges;
740
741         if (a == NULL) {
742             warn = 1;
743             continue;
744         }
745
746         range = a->elts;
747         for (i = 0; i < a->nelts; i++) {
748
749             if (s == (ngx_uint_t) range[i].start
750                 && e == (ngx_uint_t) range[i].end)
751             {
752                 ngx_memcpy(&range[i], &range[i + 1],
753                            (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
754
755                 a->nelts--;
756
757                 break;
758             }
759
760             if (s != (ngx_uint_t) range[i].start
761                 && e != (ngx_uint_t) range[i].end)
762             {
763                 continue;
764             }
765
766             warn = 1;
767         }
768     }
769
770     return warn;
771 }
772
773
774 static char *
775 ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
776     ngx_str_t *value)
777 {
778     ngx_int_t                        rc, del;
779     ngx_str_t                       *net;
780     ngx_uint_t                       i;
781     ngx_cidr_t                       cidr;
782     ngx_http_variable_value_t       *val, *old;
783
784     if (ctx->tree == NULL) {
785         ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
786         if (ctx->tree == NULL) {
787             return NGX_CONF_ERROR;
788         }
789     }
790
791     if (ngx_strcmp(value[0].data, "default") == 0) {
792         cidr.u.in.addr = 0;
793         cidr.u.in.mask = 0;
794         net = &value[0];
795
796     } else {
797         if (ngx_strcmp(value[0].data, "delete") == 0) {
798             net = &value[1];
799             del = 1;
800
801         } else {
802             net = &value[0];
803             del = 0;
804         }
805
806         if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
807             cidr.u.in.addr = 0xffffffff;
808             cidr.u.in.mask = 0xffffffff;
809
810         } else {
811             rc = ngx_ptocidr(net, &cidr);
812
813             if (rc == NGX_ERROR) {
814                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
815                                    "invalid network \"%V\"", net);
816                 return NGX_CONF_ERROR;
817             }
818
819             if (cidr.family != AF_INET) {
820                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
821                                    "\"geo\" supports IPv4 only");
822                 return NGX_CONF_ERROR;
823             }
824
825             if (rc == NGX_DONE) {
826                 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
827                                    "low address bits of %V are meaningless",
828                                    net);
829             }
830
831             cidr.u.in.addr = ntohl(cidr.u.in.addr);
832             cidr.u.in.mask = ntohl(cidr.u.in.mask);
833         }
834
835         if (del) {
836             if (ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
837                                        cidr.u.in.mask)
838                 != NGX_OK)
839             {
840                 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
841                                    "no network \"%V\" to delete", net);
842             }
843
844             return NGX_CONF_OK;
845         }
846     }
847
848     val = ngx_http_geo_value(cf, ctx, &value[1]);
849
850     if (val == NULL) {
851         return NGX_CONF_ERROR;
852     }
853
854     for (i = 2; i; i--) {
855         rc = ngx_radix32tree_insert(ctx->tree, cidr.u.in.addr, cidr.u.in.mask,
856                                     (uintptr_t) val);
857         if (rc == NGX_OK) {
858             return NGX_CONF_OK;
859         }
860
861         if (rc == NGX_ERROR) {
862             return NGX_CONF_ERROR;
863         }
864
865         /* rc == NGX_BUSY */
866
867         old  = (ngx_http_variable_value_t *)
868               ngx_radix32tree_find(ctx->tree, cidr.u.in.addr & cidr.u.in.mask);
869
870         ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
871                 "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
872                 net, val, old);
873
874         rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr, cidr.u.in.mask);
875
876         if (rc == NGX_ERROR) {
877             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
878             return NGX_CONF_ERROR;
879         }
880     }
881
882     return NGX_CONF_ERROR;
883 }
884
885
886 static ngx_http_variable_value_t *
887 ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
888     ngx_str_t *value)
889 {
890     uint32_t                         hash;
891     ngx_http_variable_value_t       *val;
892     ngx_http_variable_value_node_t  *vvn;
893
894     hash = ngx_crc32_long(value->data, value->len);
895
896     val = ngx_http_variable_value_lookup(&ctx->rbtree, value, hash);
897
898     if (val) {
899         return val;
900     }
901
902     val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t));
903     if (val == NULL) {
904         return NULL;
905     }
906
907     val->len = value->len;
908     val->data = ngx_pstrdup(ctx->pool, value);
909     if (val->data == NULL) {
910         return NULL;
911     }
912
913     val->valid = 1;
914     val->no_cacheable = 0;
915     val->not_found = 0;
916
917     vvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_http_variable_value_node_t));
918     if (vvn == NULL) {
919         return NULL;
920     }
921
922     vvn->node.key = hash;
923     vvn->len = val->len;
924     vvn->value = val;
925
926     ngx_rbtree_insert(&ctx->rbtree, &vvn->node);
927
928     return val;
929 }