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 if (r->connection->sockaddr->sa_family != AF_INET) {
182 sin = (struct sockaddr_in *) r->connection->sockaddr;
183 return ntohl(sin->sin_addr.s_addr);
186 v = ngx_http_get_flushed_variable(r, ctx->index);
188 if (v == NULL || v->not_found) {
189 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
190 "http geo not found");
195 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
196 "http geo started: %v", v);
198 return ntohl(ngx_inet_addr(v->data, v->len));
203 ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
207 ngx_str_t *value, name;
212 ngx_http_variable_t *var;
213 ngx_http_geo_ctx_t *geo;
214 ngx_http_geo_conf_ctx_t ctx;
216 value = cf->args->elts;
218 geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t));
220 return NGX_CONF_ERROR;
227 if (cf->args->nelts == 3) {
229 geo->index = ngx_http_get_variable_index(cf, &name);
230 if (geo->index == NGX_ERROR) {
231 return NGX_CONF_ERROR;
242 var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
244 return NGX_CONF_ERROR;
247 pool = ngx_create_pool(16384, cf->log);
249 return NGX_CONF_ERROR;
252 ctx.temp_pool = ngx_create_pool(16384, cf->log);
253 if (ctx.temp_pool == NULL) {
254 return NGX_CONF_ERROR;
257 ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel,
258 ngx_http_variable_value_rbtree_insert);
267 cf->handler = ngx_http_geo;
268 cf->handler_conf = conf;
270 rv = ngx_conf_parse(cf, NULL);
276 for (i = 0; i < 0x10000; i++) {
277 a = (ngx_array_t *) ctx.high->low[i].ranges;
279 if (a == NULL || a->nelts == 0) {
283 ctx.high->low[i].n = a->nelts;
285 len = a->nelts * sizeof(ngx_http_geo_range_t);
287 ctx.high->low[i].ranges = ngx_palloc(cf->pool, len);
288 if (ctx.high->low[i].ranges == NULL ){
289 return NGX_CONF_ERROR;
292 ngx_memcpy(ctx.high->low[i].ranges, a->elts, len);
295 geo->u.high = ctx.high;
297 var->get_handler = ngx_http_geo_range_variable;
298 var->data = (uintptr_t) geo;
300 ngx_destroy_pool(ctx.temp_pool);
301 ngx_destroy_pool(pool);
303 if (ctx.high->default_value == NULL) {
304 ctx.high->default_value = &ngx_http_variable_null_value;
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;
315 geo->u.tree = ctx.tree;
317 var->get_handler = ngx_http_geo_cidr_variable;
318 var->data = (uintptr_t) geo;
320 ngx_destroy_pool(ctx.temp_pool);
321 ngx_destroy_pool(pool);
323 if (ngx_radix32tree_find(ctx.tree, 0) != NGX_RADIX_NO_VALUE) {
327 if (ngx_radix32tree_insert(ctx.tree, 0, 0,
328 (uintptr_t) &ngx_http_variable_null_value)
331 return NGX_CONF_ERROR;
340 ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
343 ngx_str_t *value, file;
344 ngx_http_geo_conf_ctx_t *ctx;
348 value = cf->args->elts;
350 if (cf->args->nelts == 1) {
352 if (ngx_strcmp(value[0].data, "ranges") == 0) {
355 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
356 "the \"ranges\" directive must be "
357 "the first directive inside \"geo\" block");
361 ctx->high = ngx_pcalloc(ctx->pool,
362 sizeof(ngx_http_geo_high_ranges_t));
363 if (ctx->high == NULL) {
373 if (cf->args->nelts != 2) {
374 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
375 "invalid number of the geo parameters");
379 if (ngx_strcmp(value[0].data, "include") == 0) {
381 file.len = value[1].len++;
383 file.data = ngx_pstrdup(ctx->temp_pool, &value[1]);
384 if (file.data == NULL) {
388 if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK){
392 ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
394 rv = ngx_conf_parse(cf, &file);
400 rv = ngx_http_geo_range(cf, ctx, value);
403 rv = ngx_http_geo_cidr(cf, ctx, value);
408 ngx_reset_pool(cf->pool);
414 ngx_reset_pool(cf->pool);
416 return NGX_CONF_ERROR;
421 ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
425 in_addr_t start, end;
428 ngx_http_variable_value_t *old;
430 if (ngx_strcmp(value[0].data, "default") == 0) {
432 old = ctx->high->default_value;
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;
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);
448 if (ngx_strcmp(value[0].data, "delete") == 0) {
457 last = net->data + net->len;
459 p = ngx_strlchr(net->data, last, '-');
465 start = ngx_inet_addr(net->data, p - net->data);
467 if (start == INADDR_NONE) {
471 start = ntohl(start);
475 end = ngx_inet_addr(p, last - p);
477 if (end == INADDR_NONE) {
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);
496 ctx->value = ngx_http_geo_value(cf, ctx, &value[1]);
498 if (ctx->value == NULL) {
499 return NGX_CONF_ERROR;
504 return ngx_http_geo_add_range(cf, ctx, start, end);
508 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);
510 return NGX_CONF_ERROR;
514 /* the add procedure is optimized to add a growing up sequence */
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)
521 ngx_uint_t h, i, s, e;
523 ngx_http_geo_range_t *range;
525 for (n = start; n <= end; n += 0x10000) {
535 if ((n | 0xffff) > end) {
542 a = (ngx_array_t *) ctx->high->low[h].ranges;
545 a = ngx_array_create(ctx->temp_pool, 64,
546 sizeof(ngx_http_geo_range_t));
548 return NGX_CONF_ERROR;
551 ctx->high->low[h].ranges = (ngx_http_geo_range_t *) a;
561 if (e < (ngx_uint_t) range[i].start) {
565 if (s > (ngx_uint_t) range[i].end) {
567 /* add after the range */
569 range = ngx_array_push(a);
571 return NGX_CONF_ERROR;
576 ngx_memcpy(&range[i + 2], &range[i + 1],
577 (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
579 range[i + 1].start = (u_short) s;
580 range[i + 1].end = (u_short) e;
581 range[i + 1].value = ctx->value;
586 if (s == (ngx_uint_t) range[i].start
587 && e == (ngx_uint_t) range[i].end)
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);
593 range[i].value = ctx->value;
598 if (s > (ngx_uint_t) range[i].start
599 && e < (ngx_uint_t) range[i].end)
601 /* split the range and insert the new one */
603 range = ngx_array_push(a);
605 return NGX_CONF_ERROR;
608 range = ngx_array_push(a);
610 return NGX_CONF_ERROR;
615 ngx_memcpy(&range[i + 3], &range[i + 1],
616 (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t));
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;
622 range[i + 1].start = (u_short) s;
623 range[i + 1].end = (u_short) e;
624 range[i + 1].value = ctx->value;
626 range[i].end = (u_short) (s - 1);
631 if (s == (ngx_uint_t) range[i].start
632 && e < (ngx_uint_t) range[i].end)
634 /* shift the range start and insert the new range */
636 range = ngx_array_push(a);
638 return NGX_CONF_ERROR;
643 ngx_memcpy(&range[i + 1], &range[i],
644 (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
646 range[i + 1].start = (u_short) (e + 1);
648 range[i].start = (u_short) s;
649 range[i].end = (u_short) e;
650 range[i].value = ctx->value;
655 if (s > (ngx_uint_t) range[i].start
656 && e == (ngx_uint_t) range[i].end)
658 /* shift the range end and insert the new range */
660 range = ngx_array_push(a);
662 return NGX_CONF_ERROR;
667 ngx_memcpy(&range[i + 2], &range[i + 1],
668 (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
670 range[i + 1].start = (u_short) s;
671 range[i + 1].end = (u_short) e;
672 range[i + 1].value = ctx->value;
674 range[i].end = (u_short) (s - 1);
679 s = (ngx_uint_t) range[i].start;
680 e = (ngx_uint_t) range[i].end;
682 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
683 "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"",
685 h >> 8, h & 0xff, s >> 8, s & 0xff,
686 h >> 8, h & 0xff, e >> 8, e & 0xff);
688 return NGX_CONF_ERROR;
691 /* add the first range */
693 range = ngx_array_push(a);
695 return NGX_CONF_ERROR;
698 range->start = (u_short) s;
699 range->end = (u_short) e;
700 range->value = ctx->value;
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)
716 ngx_uint_t h, i, s, e, warn;
718 ngx_http_geo_range_t *range;
722 for (n = start; n <= end; n += 0x10000) {
732 if ((n | 0xffff) > end) {
739 a = (ngx_array_t *) ctx->high->low[h].ranges;
747 for (i = 0; i < a->nelts; i++) {
749 if (s == (ngx_uint_t) range[i].start
750 && e == (ngx_uint_t) range[i].end)
752 ngx_memcpy(&range[i], &range[i + 1],
753 (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
760 if (s != (ngx_uint_t) range[i].start
761 && e != (ngx_uint_t) range[i].end)
775 ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
782 ngx_http_variable_value_t *val, *old;
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;
791 if (ngx_strcmp(value[0].data, "default") == 0) {
797 if (ngx_strcmp(value[0].data, "delete") == 0) {
806 if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
807 cidr.u.in.addr = 0xffffffff;
808 cidr.u.in.mask = 0xffffffff;
811 rc = ngx_ptocidr(net, &cidr);
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;
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;
825 if (rc == NGX_DONE) {
826 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
827 "low address bits of %V are meaningless",
831 cidr.u.in.addr = ntohl(cidr.u.in.addr);
832 cidr.u.in.mask = ntohl(cidr.u.in.mask);
836 if (ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
840 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
841 "no network \"%V\" to delete", net);
848 val = ngx_http_geo_value(cf, ctx, &value[1]);
851 return NGX_CONF_ERROR;
854 for (i = 2; i; i--) {
855 rc = ngx_radix32tree_insert(ctx->tree, cidr.u.in.addr, cidr.u.in.mask,
861 if (rc == NGX_ERROR) {
862 return NGX_CONF_ERROR;
867 old = (ngx_http_variable_value_t *)
868 ngx_radix32tree_find(ctx->tree, cidr.u.in.addr & cidr.u.in.mask);
870 ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
871 "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
874 rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr, cidr.u.in.mask);
876 if (rc == NGX_ERROR) {
877 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
878 return NGX_CONF_ERROR;
882 return NGX_CONF_ERROR;
886 static ngx_http_variable_value_t *
887 ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
891 ngx_http_variable_value_t *val;
892 ngx_http_variable_value_node_t *vvn;
894 hash = ngx_crc32_long(value->data, value->len);
896 val = ngx_http_variable_value_lookup(&ctx->rbtree, value, hash);
902 val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t));
907 val->len = value->len;
908 val->data = ngx_pstrdup(ctx->pool, value);
909 if (val->data == NULL) {
914 val->no_cacheable = 0;
917 vvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_http_variable_value_node_t));
922 vvn->node.key = hash;
926 ngx_rbtree_insert(&ctx->rbtree, &vvn->node);