3 * Copyright (C) Igor Sysoev
7 #include <ngx_config.h>
15 ngx_http_variable_value_t *value;
16 } ngx_http_geo_range_t;
20 ngx_http_geo_range_t *ranges;
22 } ngx_http_geo_low_ranges_t;
26 ngx_http_geo_low_ranges_t low[0x10000];
27 ngx_http_variable_value_t *default_value;
28 } ngx_http_geo_high_ranges_t;
32 ngx_http_variable_value_t *value;
34 ngx_http_geo_high_ranges_t *high;
35 ngx_radix_tree_t *tree;
37 ngx_rbtree_node_t sentinel;
39 ngx_pool_t *temp_pool;
40 } ngx_http_geo_conf_ctx_t;
45 ngx_radix_tree_t *tree;
46 ngx_http_geo_high_ranges_t *high;
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,
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,
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);
69 static ngx_command_t ngx_http_geo_commands[] = {
72 NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
74 NGX_HTTP_MAIN_CONF_OFFSET,
82 static ngx_http_module_t ngx_http_geo_module_ctx = {
83 NULL, /* preconfiguration */
84 NULL, /* postconfiguration */
86 NULL, /* create main configuration */
87 NULL, /* init main configuration */
89 NULL, /* create server configuration */
90 NULL, /* merge server configuration */
92 NULL, /* create location configuration */
93 NULL /* merge location configuration */
97 ngx_module_t ngx_http_geo_module = {
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
116 ngx_http_geo_cidr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
119 ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
121 ngx_http_variable_value_t *vv;
123 vv = (ngx_http_variable_value_t *)
124 ngx_radix32tree_find(ctx->u.tree, ngx_http_geo_addr(r, ctx));
128 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
136 ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
139 ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
143 ngx_http_geo_range_t *range;
145 *v = *ctx->u.high->default_value;
147 addr = ngx_http_geo_addr(r, ctx);
149 range = ctx->u.high->low[addr >> 16].ranges;
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)
157 *v = *range[i].value;
161 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
169 ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx)
171 struct sockaddr_in *sin;
172 ngx_http_variable_value_t *v;
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);
178 sin = (struct sockaddr_in *) r->connection->sockaddr;
179 return ntohl(sin->sin_addr.s_addr);
182 v = ngx_http_get_flushed_variable(r, ctx->index);
184 if (v == NULL || v->not_found) {
185 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
186 "http geo not found");
191 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
192 "http geo started: %v", v);
194 return ntohl(ngx_inet_addr(v->data, v->len));
199 ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
203 ngx_str_t *value, name;
208 ngx_http_variable_t *var;
209 ngx_http_geo_ctx_t *geo;
210 ngx_http_geo_conf_ctx_t ctx;
212 value = cf->args->elts;
214 geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t));
216 return NGX_CONF_ERROR;
223 if (cf->args->nelts == 3) {
225 geo->index = ngx_http_get_variable_index(cf, &name);
226 if (geo->index == NGX_ERROR) {
227 return NGX_CONF_ERROR;
238 var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
240 return NGX_CONF_ERROR;
243 pool = ngx_create_pool(16384, cf->log);
245 return NGX_CONF_ERROR;
248 ctx.temp_pool = ngx_create_pool(16384, cf->log);
249 if (ctx.temp_pool == NULL) {
250 return NGX_CONF_ERROR;
253 ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel,
254 ngx_http_variable_value_rbtree_insert);
263 cf->handler = ngx_http_geo;
264 cf->handler_conf = conf;
266 rv = ngx_conf_parse(cf, NULL);
272 for (i = 0; i < 0x10000; i++) {
273 a = (ngx_array_t *) ctx.high->low[i].ranges;
275 if (a == NULL || a->nelts == 0) {
279 ctx.high->low[i].n = a->nelts;
281 len = a->nelts * sizeof(ngx_http_geo_range_t);
283 ctx.high->low[i].ranges = ngx_palloc(cf->pool, len);
284 if (ctx.high->low[i].ranges == NULL ){
285 return NGX_CONF_ERROR;
288 ngx_memcpy(ctx.high->low[i].ranges, a->elts, len);
291 geo->u.high = ctx.high;
293 var->get_handler = ngx_http_geo_range_variable;
294 var->data = (uintptr_t) geo;
296 ngx_destroy_pool(ctx.temp_pool);
297 ngx_destroy_pool(pool);
299 if (ctx.high->default_value == NULL) {
300 ctx.high->default_value = &ngx_http_variable_null_value;
304 if (ctx.tree == NULL) {
305 ctx.tree = ngx_radix_tree_create(cf->pool, -1);
306 if (ctx.tree == NULL) {
307 return NGX_CONF_ERROR;
311 geo->u.tree = ctx.tree;
313 var->get_handler = ngx_http_geo_cidr_variable;
314 var->data = (uintptr_t) geo;
316 ngx_destroy_pool(ctx.temp_pool);
317 ngx_destroy_pool(pool);
319 if (ngx_radix32tree_find(ctx.tree, 0) != NGX_RADIX_NO_VALUE) {
323 if (ngx_radix32tree_insert(ctx.tree, 0, 0,
324 (uintptr_t) &ngx_http_variable_null_value)
327 return NGX_CONF_ERROR;
336 ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
339 ngx_str_t *value, file;
340 ngx_http_geo_conf_ctx_t *ctx;
344 value = cf->args->elts;
346 if (cf->args->nelts == 1) {
348 if (ngx_strcmp(value[0].data, "ranges") == 0) {
351 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
352 "the \"ranges\" directive must be "
353 "the first directive inside \"geo\" block");
357 ctx->high = ngx_pcalloc(ctx->pool,
358 sizeof(ngx_http_geo_high_ranges_t));
359 if (ctx->high == NULL) {
369 if (cf->args->nelts != 2) {
370 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
371 "invalid number of the geo parameters");
375 if (ngx_strcmp(value[0].data, "include") == 0) {
377 file.len = value[1].len++;
379 file.data = ngx_pstrdup(ctx->temp_pool, &value[1]);
380 if (file.data == NULL) {
384 if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK){
388 ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
390 rv = ngx_conf_parse(cf, &file);
396 rv = ngx_http_geo_range(cf, ctx, value);
399 rv = ngx_http_geo_cidr(cf, ctx, value);
404 ngx_reset_pool(cf->pool);
410 ngx_reset_pool(cf->pool);
412 return NGX_CONF_ERROR;
417 ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
421 in_addr_t start, end;
424 ngx_http_variable_value_t *old;
426 if (ngx_strcmp(value[0].data, "default") == 0) {
428 old = ctx->high->default_value;
430 ctx->high->default_value = ngx_http_geo_value(cf, ctx, &value[1]);
431 if (ctx->high->default_value == NULL) {
432 return NGX_CONF_ERROR;
436 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
437 "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
438 &value[0], ctx->high->default_value, old);
444 if (ngx_strcmp(value[0].data, "delete") == 0) {
453 last = net->data + net->len;
455 p = ngx_strlchr(net->data, last, '-');
461 start = ngx_inet_addr(net->data, p - net->data);
463 if (start == INADDR_NONE) {
467 start = ntohl(start);
471 end = ngx_inet_addr(p, last - p);
473 if (end == INADDR_NONE) {
484 if (ngx_http_geo_delete_range(cf, ctx, start, end)) {
485 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
486 "no address range \"%V\" to delete", net);
492 ctx->value = ngx_http_geo_value(cf, ctx, &value[1]);
494 if (ctx->value == NULL) {
495 return NGX_CONF_ERROR;
500 return ngx_http_geo_add_range(cf, ctx, start, end);
504 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);
506 return NGX_CONF_ERROR;
510 /* the add procedure is optimized to add a growing up sequence */
513 ngx_http_geo_add_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
514 in_addr_t start, in_addr_t end)
517 ngx_uint_t h, i, s, e;
519 ngx_http_geo_range_t *range;
521 for (n = start; n <= end; n += 0x10000) {
531 if ((n | 0xffff) > end) {
538 a = (ngx_array_t *) ctx->high->low[h].ranges;
541 a = ngx_array_create(ctx->temp_pool, 64,
542 sizeof(ngx_http_geo_range_t));
544 return NGX_CONF_ERROR;
547 ctx->high->low[h].ranges = (ngx_http_geo_range_t *) a;
557 if (e < (ngx_uint_t) range[i].start) {
561 if (s > (ngx_uint_t) range[i].end) {
563 /* add after the range */
565 range = ngx_array_push(a);
567 return NGX_CONF_ERROR;
572 ngx_memcpy(&range[i + 2], &range[i + 1],
573 (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
575 range[i + 1].start = (u_short) s;
576 range[i + 1].end = (u_short) e;
577 range[i + 1].value = ctx->value;
582 if (s == (ngx_uint_t) range[i].start
583 && e == (ngx_uint_t) range[i].end)
585 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
586 "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
587 ctx->net, ctx->value, range[i].value);
589 range[i].value = ctx->value;
594 if (s > (ngx_uint_t) range[i].start
595 && e < (ngx_uint_t) range[i].end)
597 /* split the range and insert the new one */
599 range = ngx_array_push(a);
601 return NGX_CONF_ERROR;
604 range = ngx_array_push(a);
606 return NGX_CONF_ERROR;
611 ngx_memcpy(&range[i + 3], &range[i + 1],
612 (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t));
614 range[i + 2].start = (u_short) (e + 1);
615 range[i + 2].end = range[i].end;
616 range[i + 2].value = range[i].value;
618 range[i + 1].start = (u_short) s;
619 range[i + 1].end = (u_short) e;
620 range[i + 1].value = ctx->value;
622 range[i].end = (u_short) (s - 1);
627 if (s == (ngx_uint_t) range[i].start
628 && e < (ngx_uint_t) range[i].end)
630 /* shift the range start and insert the new range */
632 range = ngx_array_push(a);
634 return NGX_CONF_ERROR;
639 ngx_memcpy(&range[i + 1], &range[i],
640 (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
642 range[i + 1].start = (u_short) (e + 1);
644 range[i].start = (u_short) s;
645 range[i].end = (u_short) e;
646 range[i].value = ctx->value;
651 if (s > (ngx_uint_t) range[i].start
652 && e == (ngx_uint_t) range[i].end)
654 /* shift the range end and insert the new range */
656 range = ngx_array_push(a);
658 return NGX_CONF_ERROR;
663 ngx_memcpy(&range[i + 2], &range[i + 1],
664 (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
666 range[i + 1].start = (u_short) s;
667 range[i + 1].end = (u_short) e;
668 range[i + 1].value = ctx->value;
670 range[i].end = (u_short) (s - 1);
675 s = (ngx_uint_t) range[i].start;
676 e = (ngx_uint_t) range[i].end;
678 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
679 "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"",
681 h >> 8, h & 0xff, s >> 8, s & 0xff,
682 h >> 8, h & 0xff, e >> 8, e & 0xff);
684 return NGX_CONF_ERROR;
687 /* add the first range */
689 range = ngx_array_push(a);
691 return NGX_CONF_ERROR;
694 range->start = (u_short) s;
695 range->end = (u_short) e;
696 range->value = ctx->value;
708 ngx_http_geo_delete_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
709 in_addr_t start, in_addr_t end)
712 ngx_uint_t h, i, s, e, warn;
714 ngx_http_geo_range_t *range;
718 for (n = start; n <= end; n += 0x10000) {
728 if ((n | 0xffff) > end) {
735 a = (ngx_array_t *) ctx->high->low[h].ranges;
743 for (i = 0; i < a->nelts; i++) {
745 if (s == (ngx_uint_t) range[i].start
746 && e == (ngx_uint_t) range[i].end)
748 ngx_memcpy(&range[i], &range[i + 1],
749 (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
756 if (s != (ngx_uint_t) range[i].start
757 && e != (ngx_uint_t) range[i].end)
771 ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
777 ngx_inet_cidr_t cidrin;
778 ngx_http_variable_value_t *val, *old;
780 if (ctx->tree == NULL) {
781 ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
782 if (ctx->tree == NULL) {
783 return NGX_CONF_ERROR;
787 if (ngx_strcmp(value[0].data, "default") == 0) {
793 if (ngx_strcmp(value[0].data, "delete") == 0) {
802 if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
803 cidrin.addr = 0xffffffff;
804 cidrin.mask = 0xffffffff;
807 rc = ngx_ptocidr(net, &cidrin);
809 if (rc == NGX_ERROR) {
810 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
811 "invalid network \"%V\"", net);
812 return NGX_CONF_ERROR;
815 if (rc == NGX_DONE) {
816 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
817 "low address bits of %V are meaningless",
821 cidrin.addr = ntohl(cidrin.addr);
822 cidrin.mask = ntohl(cidrin.mask);
826 if (ngx_radix32tree_delete(ctx->tree, cidrin.addr, cidrin.mask)
829 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
830 "no network \"%V\" to delete", net);
837 val = ngx_http_geo_value(cf, ctx, &value[1]);
840 return NGX_CONF_ERROR;
843 for (i = 2; i; i--) {
844 rc = ngx_radix32tree_insert(ctx->tree, cidrin.addr, cidrin.mask,
850 if (rc == NGX_ERROR) {
851 return NGX_CONF_ERROR;
856 old = (ngx_http_variable_value_t *)
857 ngx_radix32tree_find(ctx->tree, cidrin.addr & cidrin.mask);
859 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
860 "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
863 rc = ngx_radix32tree_delete(ctx->tree, cidrin.addr, cidrin.mask);
865 if (rc == NGX_ERROR) {
866 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
867 return NGX_CONF_ERROR;
871 return NGX_CONF_ERROR;
875 static ngx_http_variable_value_t *
876 ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
880 ngx_http_variable_value_t *val;
881 ngx_http_variable_value_node_t *vvn;
883 hash = ngx_crc32_long(value->data, value->len);
885 val = ngx_http_variable_value_lookup(&ctx->rbtree, value, hash);
891 val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t));
896 val->len = value->len;
897 val->data = ngx_pstrdup(ctx->pool, value);
898 if (val->data == NULL) {
903 val->no_cacheable = 0;
906 vvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_http_variable_value_node_t));
911 vvn->node.key = hash;
915 ngx_rbtree_insert(&ctx->rbtree, &vvn->node);