# HG changeset patch # User Igor Sysoev # Date 1277827580 0 # Node ID 1eea82e360531360d3b0035dbe7a26d6972d8bed # Parent 515d5091701613efa9552b175d7552e81156dbbb binary geo ranges base cache diff --git a/src/http/modules/ngx_http_geo_module.c b/src/http/modules/ngx_http_geo_module.c --- a/src/http/modules/ngx_http_geo_module.c +++ b/src/http/modules/ngx_http_geo_module.c @@ -17,7 +17,7 @@ typedef struct { typedef struct { - ngx_http_geo_range_t *low[0x10000]; + ngx_http_geo_range_t **low; ngx_http_variable_value_t *default_value; } ngx_http_geo_high_ranges_t; @@ -25,26 +25,38 @@ typedef struct { typedef struct { ngx_str_node_t sn; ngx_http_variable_value_t *value; + size_t offset; } ngx_http_geo_variable_value_node_t; typedef struct { ngx_http_variable_value_t *value; ngx_str_t *net; - ngx_http_geo_high_ranges_t *high; + ngx_http_geo_high_ranges_t high; ngx_radix_tree_t *tree; ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; ngx_array_t *proxies; ngx_pool_t *pool; ngx_pool_t *temp_pool; + + size_t data_size; + + ngx_str_t include_name; + ngx_uint_t includes; + ngx_uint_t entries; + + unsigned ranges:1; + unsigned outside_entries:1; + unsigned allow_binary_include:1; + unsigned binary_include:1; } ngx_http_geo_conf_ctx_t; typedef struct { union { ngx_radix_tree_t *tree; - ngx_http_geo_high_ranges_t *high; + ngx_http_geo_high_ranges_t high; } u; ngx_array_t *proxies; @@ -73,6 +85,13 @@ static char *ngx_http_geo_add_proxy(ngx_ ngx_http_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr); static ngx_int_t ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr); +static char *ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, + ngx_str_t *name); +static ngx_int_t ngx_http_geo_include_binary_base(ngx_conf_t *cf, + ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *name); +static void ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx); +static u_char *ngx_http_geo_copy_values(u_char *base, u_char *p, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); static ngx_command_t ngx_http_geo_commands[] = { @@ -119,6 +138,20 @@ ngx_module_t ngx_http_geo_module = { }; +typedef struct { + u_char GEORNG[6]; + u_char version; + u_char ptr_size; + uint32_t endianess; + uint32_t crc32; +} ngx_http_geo_header_t; + + +static ngx_http_geo_header_t ngx_http_geo_header = { + { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0 +}; + + /* AF_INET only */ static ngx_int_t @@ -151,11 +184,11 @@ ngx_http_geo_range_variable(ngx_http_req ngx_uint_t n; ngx_http_geo_range_t *range; - *v = *ctx->u.high->default_value; + *v = *ctx->u.high.default_value; addr = ngx_http_geo_addr(r, ctx); - range = ctx->u.high->low[addr >> 16]; + range = ctx->u.high.low[addr >> 16]; if (range) { n = addr & 0xffff; @@ -304,6 +337,8 @@ ngx_http_geo_block(ngx_conf_t *cf, ngx_c return NGX_CONF_ERROR; } + ngx_memzero(&ctx, sizeof(ngx_http_geo_conf_ctx_t)); + ctx.temp_pool = ngx_create_pool(16384, cf->log); if (ctx.temp_pool == NULL) { return NGX_CONF_ERROR; @@ -311,10 +346,11 @@ ngx_http_geo_block(ngx_conf_t *cf, ngx_c ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value); - ctx.high = NULL; - ctx.tree = NULL; - ctx.proxies = NULL; ctx.pool = cf->pool; + ctx.data_size = sizeof(ngx_http_geo_header_t) + + sizeof(ngx_http_variable_value_t) + + 0x10000 * sizeof(ngx_http_geo_range_t *); + ctx.allow_binary_include = 1; save = *cf; cf->pool = pool; @@ -328,24 +364,35 @@ ngx_http_geo_block(ngx_conf_t *cf, ngx_c geo->proxies = ctx.proxies; - if (ctx.high) { + if (ctx.high.low) { + + if (!ctx.binary_include) { + for (i = 0; i < 0x10000; i++) { + a = (ngx_array_t *) ctx.high.low[i]; + + if (a == NULL || a->nelts == 0) { + continue; + } - for (i = 0; i < 0x10000; i++) { - a = (ngx_array_t *) ctx.high->low[i]; + len = a->nelts * sizeof(ngx_http_geo_range_t); - if (a == NULL || a->nelts == 0) { - continue; + ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); + if (ctx.high.low[i] == NULL) { + return NGX_CONF_ERROR; + } + + p = (void **) ngx_cpymem(ctx.high.low[i], a->elts, len); + *p = NULL; + ctx.data_size += len + sizeof(void *); } - len = a->nelts * sizeof(ngx_http_geo_range_t); - - ctx.high->low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); - if (ctx.high->low[i] == NULL) { - return NGX_CONF_ERROR; + if (ctx.allow_binary_include + && !ctx.outside_entries + && ctx.entries > 100000 + && ctx.includes == 1) + { + ngx_http_geo_create_binary_base(&ctx); } - - p = (void **) ngx_cpymem(ctx.high->low[i], a->elts, len); - *p = NULL; } geo->u.high = ctx.high; @@ -353,13 +400,13 @@ ngx_http_geo_block(ngx_conf_t *cf, ngx_c var->get_handler = ngx_http_geo_range_variable; var->data = (uintptr_t) geo; + if (ctx.high.default_value == NULL) { + ctx.high.default_value = &ngx_http_variable_null_value; + } + ngx_destroy_pool(ctx.temp_pool); ngx_destroy_pool(pool); - if (ctx.high->default_value == NULL) { - ctx.high->default_value = &ngx_http_variable_null_value; - } - } else { if (ctx.tree == NULL) { ctx.tree = ngx_radix_tree_create(cf->pool, -1); @@ -396,7 +443,7 @@ static char * ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) { char *rv; - ngx_str_t *value, file; + ngx_str_t *value; ngx_cidr_t cidr; ngx_http_geo_conf_ctx_t *ctx; @@ -415,11 +462,7 @@ ngx_http_geo(ngx_conf_t *cf, ngx_command goto failed; } - ctx->high = ngx_pcalloc(ctx->pool, - sizeof(ngx_http_geo_high_ranges_t)); - if (ctx->high == NULL) { - goto failed; - } + ctx->ranges = 1; rv = NGX_CONF_OK; @@ -435,20 +478,7 @@ ngx_http_geo(ngx_conf_t *cf, ngx_command if (ngx_strcmp(value[0].data, "include") == 0) { - file.len = value[1].len++; - - file.data = ngx_pstrdup(ctx->temp_pool, &value[1]); - if (file.data == NULL) { - goto failed; - } - - if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) { - goto failed; - } - - ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); - - rv = ngx_conf_parse(cf, &file); + rv = ngx_http_geo_include(cf, ctx, &value[1]); goto done; @@ -463,7 +493,7 @@ ngx_http_geo(ngx_conf_t *cf, ngx_command goto done; } - if (ctx->high) { + if (ctx->ranges) { rv = ngx_http_geo_range(cf, ctx, value); } else { @@ -495,20 +525,38 @@ ngx_http_geo_range(ngx_conf_t *cf, ngx_h if (ngx_strcmp(value[0].data, "default") == 0) { - if (ctx->high->default_value) { + if (ctx->high.default_value) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate default geo range value: \"%V\", old value: \"%v\"", - &value[1], ctx->high->default_value); + &value[1], ctx->high.default_value); } - ctx->high->default_value = ngx_http_geo_value(cf, ctx, &value[1]); - if (ctx->high->default_value == NULL) { + ctx->high.default_value = ngx_http_geo_value(cf, ctx, &value[1]); + if (ctx->high.default_value == NULL) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } + if (ctx->binary_include) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "binary geo range base \"%s\" may not be mixed with usual entries", + ctx->include_name.data); + return NGX_CONF_ERROR; + } + + if (ctx->high.low == NULL) { + ctx->high.low = ngx_pcalloc(ctx->pool, + 0x10000 * sizeof(ngx_http_geo_range_t *)); + if (ctx->high.low == NULL) { + return NGX_CONF_ERROR; + } + } + + ctx->entries++; + ctx->outside_entries = 1; + if (ngx_strcmp(value[0].data, "delete") == 0) { net = &value[1]; del = 1; @@ -603,7 +651,7 @@ ngx_http_geo_add_range(ngx_conf_t *cf, n e = 0xffff; } - a = (ngx_array_t *) ctx->high->low[h]; + a = (ngx_array_t *) ctx->high.low[h]; if (a == NULL) { a = ngx_array_create(ctx->temp_pool, 64, @@ -612,7 +660,7 @@ ngx_http_geo_add_range(ngx_conf_t *cf, n return NGX_CONF_ERROR; } - ctx->high->low[h] = (ngx_http_geo_range_t *) a; + ctx->high.low[h] = (ngx_http_geo_range_t *) a; } i = a->nelts; @@ -800,7 +848,7 @@ ngx_http_geo_delete_range(ngx_conf_t *cf e = 0xffff; } - a = (ngx_array_t *) ctx->high->low[h]; + a = (ngx_array_t *) ctx->high.low[h]; if (a == NULL) { warn = 1; @@ -964,9 +1012,13 @@ ngx_http_geo_value(ngx_conf_t *cf, ngx_h gvvn->sn.str.len = val->len; gvvn->sn.str.data = val->data; gvvn->value = val; + gvvn->offset = 0; ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node); + ctx->data_size += ngx_align(sizeof(ngx_http_variable_value_t) + value->len, + sizeof(void *)); + return val; } @@ -1030,3 +1082,306 @@ ngx_http_geo_cidr_value(ngx_conf_t *cf, return NGX_OK; } + + +static char * +ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, + ngx_str_t *name) +{ + char *rv; + ngx_str_t file; + + file.len = name->len + 4; + file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5); + if (file.data == NULL) { + return NGX_CONF_ERROR; + } + + ngx_sprintf(file.data, "%V.bin%Z", name); + + if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); + + switch (ngx_http_geo_include_binary_base(cf, ctx, &file)) { + case NGX_OK: + return NGX_CONF_OK; + case NGX_ERROR: + return NGX_CONF_ERROR; + default: + break; + } + + file.len -= 4; + file.data[file.len] = '\0'; + + ctx->include_name = file; + + if (ctx->outside_entries) { + ctx->allow_binary_include = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); + + rv = ngx_conf_parse(cf, &file); + + ctx->includes++; + ctx->outside_entries = 0; + + return rv; +} + + +static ngx_int_t +ngx_http_geo_include_binary_base(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, + ngx_str_t *name) +{ + u_char *base; + size_t size, len; + ssize_t n; + uint32_t crc32; + ngx_err_t err; + ngx_int_t rc; + ngx_uint_t i; + ngx_file_t file; + ngx_file_info_t fi; + ngx_http_geo_range_t *range, **ranges; + ngx_http_geo_header_t *header; + ngx_http_variable_value_t *vv; + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = *name; + file.log = cf->log; + + file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, 0, 0); + if (file.fd == NGX_INVALID_FILE) { + err = ngx_errno; + if (err != NGX_ENOENT) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, err, + ngx_open_file_n " \"%s\" failed", name->data); + } + return NGX_DECLINED; + } + + if (ctx->outside_entries) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "binary geo range base \"%s\" may not be mixed with usual entries", + name->data); + rc = NGX_ERROR; + goto done; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%s\" failed", name->data); + goto failed; + } + + size = (size_t) ngx_file_size(&fi); + + base = ngx_palloc(ctx->pool, size); + if (base == NULL) { + goto failed; + } + + n = ngx_read_file(&file, base, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%s\" failed", name->data); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%s\" returned only %z bytes instead of %z", + name->data, n, size); + goto failed; + } + + header = (ngx_http_geo_header_t *) base; + + if (ngx_memcmp(&ngx_http_geo_header, header, 12) != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "incompatible binary geo range base \"%s\"", name->data); + goto failed; + } + + ngx_crc32_init(crc32); + + vv = (ngx_http_variable_value_t *) (base + sizeof(ngx_http_geo_header_t)); + + while(vv->data) { + len = ngx_align(sizeof(ngx_http_variable_value_t) + vv->len, + sizeof(void *)); + ngx_crc32_update(&crc32, (u_char *) vv, len); + vv->data += (size_t) base; + vv = (ngx_http_variable_value_t *) ((u_char *) vv + len); + } + ngx_crc32_update(&crc32, (u_char *) vv, sizeof(ngx_http_variable_value_t)); + vv++; + + ranges = (ngx_http_geo_range_t **) vv; + + for (i = 0; i < 0x10000; i++) { + ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *)); + if (ranges[i]) { + ranges[i] = (ngx_http_geo_range_t *) + ((u_char *) ranges[i] + (size_t) base); + } + } + + range = (ngx_http_geo_range_t *) &ranges[0x10000]; + + while ((u_char *) range < base + size) { + while (range->value) { + ngx_crc32_update(&crc32, (u_char *) range, + sizeof(ngx_http_geo_range_t)); + range->value = (ngx_http_variable_value_t *) + ((u_char *) range->value + (size_t) base); + range++; + } + ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *)); + range = (ngx_http_geo_range_t *) ((u_char *) range + sizeof(void *)); + } + + ngx_crc32_final(crc32); + + if (crc32 != header->crc32) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "CRC32 mismatch in binary geo range base \"%s\"", name->data); + goto failed; + } + + ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, + "using binary geo range base \"%s\"", name->data); + + ctx->include_name = *name; + ctx->binary_include = 1; + ctx->high.low = ranges; + rc = NGX_OK; + + goto done; + +failed: + + rc = NGX_DECLINED; + +done: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", name->data); + } + + return rc; +} + + +static void +ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx) +{ + u_char *p; + uint32_t hash; + ngx_str_t s; + ngx_uint_t i; + ngx_file_mapping_t fm; + ngx_http_geo_range_t *r, *range, **ranges; + ngx_http_geo_header_t *header; + ngx_http_geo_variable_value_node_t *gvvn; + + fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5); + if (fm.name == NULL) { + return; + } + + ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name); + + fm.size = ctx->data_size; + fm.log = ctx->pool->log; + + ngx_log_error(NGX_LOG_NOTICE, fm.log, 0, + "creating binary geo range base \"%s\"", fm.name); + + if (ngx_create_file_mapping(&fm) != NGX_OK) { + return; + } + + p = ngx_cpymem(fm.addr, &ngx_http_geo_header, + sizeof(ngx_http_geo_header_t)); + + p = ngx_http_geo_copy_values(fm.addr, p, ctx->rbtree.root, + ctx->rbtree.sentinel); + + p += sizeof(ngx_http_variable_value_t); + + ranges = (ngx_http_geo_range_t **) p; + + p += 0x10000 * sizeof(ngx_http_geo_range_t *); + + for (i = 0; i < 0x10000; i++) { + r = ctx->high.low[i]; + if (r == NULL) { + continue; + } + + range = (ngx_http_geo_range_t *) p; + ranges[i] = (ngx_http_geo_range_t *) (p - (u_char *) fm.addr); + + do { + s.len = r->value->len; + s.data = r->value->data; + hash = ngx_crc32_long(s.data, s.len); + gvvn = (ngx_http_geo_variable_value_node_t *) + ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash); + + range->value = (ngx_http_variable_value_t *) gvvn->offset; + range->start = r->start; + range->end = r->end; + range++; + + } while ((++r)->value); + + range->value = NULL; + + p = (u_char *) range + sizeof(void *); + } + + header = fm.addr; + header->crc32 = ngx_crc32_long((u_char *) fm.addr + + sizeof(ngx_http_geo_header_t), + fm.size - sizeof(ngx_http_geo_header_t)); + + ngx_close_file_mapping(&fm); +} + + +static u_char * +ngx_http_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node, + ngx_rbtree_node_t *sentinel) +{ + ngx_http_variable_value_t *vv; + ngx_http_geo_variable_value_node_t *gvvn; + + if (node == sentinel) { + return p; + } + + gvvn = (ngx_http_geo_variable_value_node_t *) node; + gvvn->offset = p - base; + + vv = (ngx_http_variable_value_t *) p; + *vv = *gvvn->value; + p += sizeof(ngx_http_variable_value_t); + vv->data = (u_char *) (p - base); + + p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len); + + p = ngx_align_ptr(p, sizeof(void *)); + + p = ngx_http_geo_copy_values(base, p, node->left, sentinel); + + return ngx_http_geo_copy_values(base, p, node->right, sentinel); +}