This source file includes following definitions.
- ngx_http_geo_cidr_variable
 
- ngx_http_geo_range_variable
 
- ngx_http_geo_addr
 
- ngx_http_geo_real_addr
 
- ngx_http_geo_block
 
- ngx_http_geo
 
- ngx_http_geo_range
 
- ngx_http_geo_add_range
 
- ngx_http_geo_delete_range
 
- ngx_http_geo_cidr
 
- ngx_http_geo_cidr_add
 
- ngx_http_geo_value
 
- ngx_http_geo_add_proxy
 
- ngx_http_geo_cidr_value
 
- ngx_http_geo_include
 
- ngx_http_geo_include_binary_base
 
- ngx_http_geo_create_binary_base
 
- ngx_http_geo_copy_values
 
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
    ngx_http_variable_value_t       *value;
    u_short                          start;
    u_short                          end;
} ngx_http_geo_range_t;
typedef struct {
    ngx_radix_tree_t                *tree;
#if (NGX_HAVE_INET6)
    ngx_radix_tree_t                *tree6;
#endif
} ngx_http_geo_trees_t;
typedef struct {
    ngx_http_geo_range_t           **low;
    ngx_http_variable_value_t       *default_value;
} ngx_http_geo_high_ranges_t;
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_radix_tree_t                *tree;
#if (NGX_HAVE_INET6)
    ngx_radix_tree_t                *tree6;
#endif
    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;
    unsigned                         proxy_recursive:1;
} ngx_http_geo_conf_ctx_t;
typedef struct {
    union {
        ngx_http_geo_trees_t         trees;
        ngx_http_geo_high_ranges_t   high;
    } u;
    ngx_array_t                     *proxies;
    unsigned                         proxy_recursive:1;
    ngx_int_t                        index;
} ngx_http_geo_ctx_t;
static ngx_int_t ngx_http_geo_addr(ngx_http_request_t *r,
    ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr);
static ngx_int_t ngx_http_geo_real_addr(ngx_http_request_t *r,
    ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr);
static char *ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
static char *ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    ngx_str_t *value);
static char *ngx_http_geo_add_range(ngx_conf_t *cf,
    ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
static ngx_uint_t ngx_http_geo_delete_range(ngx_conf_t *cf,
    ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
static char *ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    ngx_str_t *value);
static char *ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net);
static ngx_http_variable_value_t *ngx_http_geo_value(ngx_conf_t *cf,
    ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value);
static char *ngx_http_geo_add_proxy(ngx_conf_t *cf,
    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[] = {
    { ngx_string("geo"),
      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
      ngx_http_geo_block,
      NGX_HTTP_MAIN_CONF_OFFSET,
      0,
      NULL },
      ngx_null_command
};
static ngx_http_module_t  ngx_http_geo_module_ctx = {
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL                                   
};
ngx_module_t  ngx_http_geo_module = {
    NGX_MODULE_V1,
    &ngx_http_geo_module_ctx,              
    ngx_http_geo_commands,                 
    NGX_HTTP_MODULE,                       
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NULL,                                  
    NGX_MODULE_V1_PADDING
};
typedef struct {
    u_char    GEORNG[6];
    u_char    version;
    u_char    ptr_size;
    uint32_t  endianness;
    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
};
static ngx_int_t
ngx_http_geo_cidr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
    uintptr_t data)
{
    ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
    in_addr_t                   inaddr;
    ngx_addr_t                  addr;
    struct sockaddr_in         *sin;
    ngx_http_variable_value_t  *vv;
#if (NGX_HAVE_INET6)
    u_char                     *p;
    struct in6_addr            *inaddr6;
#endif
    if (ngx_http_geo_addr(r, ctx, &addr) != NGX_OK) {
        vv = (ngx_http_variable_value_t *)
                  ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE);
        goto done;
    }
    switch (addr.sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
    case AF_INET6:
        inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
        p = inaddr6->s6_addr;
        if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
            inaddr = p[12] << 24;
            inaddr += p[13] << 16;
            inaddr += p[14] << 8;
            inaddr += p[15];
            vv = (ngx_http_variable_value_t *)
                      ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
        } else {
            vv = (ngx_http_variable_value_t *)
                      ngx_radix128tree_find(ctx->u.trees.tree6, p);
        }
        break;
#endif
    default: 
        sin = (struct sockaddr_in *) addr.sockaddr;
        inaddr = ntohl(sin->sin_addr.s_addr);
        vv = (ngx_http_variable_value_t *)
                  ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
        break;
    }
done:
    *v = *vv;
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http geo: %v", v);
    return NGX_OK;
}
static ngx_int_t
ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
    uintptr_t data)
{
    ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
    in_addr_t              inaddr;
    ngx_addr_t             addr;
    ngx_uint_t             n;
    struct sockaddr_in    *sin;
    ngx_http_geo_range_t  *range;
#if (NGX_HAVE_INET6)
    u_char                *p;
    struct in6_addr       *inaddr6;
#endif
    *v = *ctx->u.high.default_value;
    if (ngx_http_geo_addr(r, ctx, &addr) == NGX_OK) {
        switch (addr.sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
        case AF_INET6:
            inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
            if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
                p = inaddr6->s6_addr;
                inaddr = p[12] << 24;
                inaddr += p[13] << 16;
                inaddr += p[14] << 8;
                inaddr += p[15];
            } else {
                inaddr = INADDR_NONE;
            }
            break;
#endif
        default: 
            sin = (struct sockaddr_in *) addr.sockaddr;
            inaddr = ntohl(sin->sin_addr.s_addr);
            break;
        }
    } else {
        inaddr = INADDR_NONE;
    }
    if (ctx->u.high.low) {
        range = ctx->u.high.low[inaddr >> 16];
        if (range) {
            n = inaddr & 0xffff;
            do {
                if (n >= (ngx_uint_t) range->start
                    && n <= (ngx_uint_t) range->end)
                {
                    *v = *range->value;
                    break;
                }
            } while ((++range)->value);
        }
    }
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http geo: %v", v);
    return NGX_OK;
}
static ngx_int_t
ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx,
    ngx_addr_t *addr)
{
    ngx_array_t  *xfwd;
    if (ngx_http_geo_real_addr(r, ctx, addr) != NGX_OK) {
        return NGX_ERROR;
    }
    xfwd = &r->headers_in.x_forwarded_for;
    if (xfwd->nelts > 0 && ctx->proxies != NULL) {
        (void) ngx_http_get_forwarded_addr(r, addr, xfwd, NULL,
                                           ctx->proxies, ctx->proxy_recursive);
    }
    return NGX_OK;
}
static ngx_int_t
ngx_http_geo_real_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx,
    ngx_addr_t *addr)
{
    ngx_http_variable_value_t  *v;
    if (ctx->index == -1) {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http geo started: %V", &r->connection->addr_text);
        addr->sockaddr = r->connection->sockaddr;
        addr->socklen = r->connection->socklen;
        
        return NGX_OK;
    }
    v = ngx_http_get_flushed_variable(r, ctx->index);
    if (v == NULL || v->not_found) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http geo not found");
        return NGX_ERROR;
    }
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http geo started: %v", v);
    if (ngx_parse_addr(r->pool, addr, v->data, v->len) == NGX_OK) {
        return NGX_OK;
    }
    return NGX_ERROR;
}
static char *
ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char                     *rv;
    size_t                    len;
    ngx_str_t                *value, name;
    ngx_uint_t                i;
    ngx_conf_t                save;
    ngx_pool_t               *pool;
    ngx_array_t              *a;
    ngx_http_variable_t      *var;
    ngx_http_geo_ctx_t       *geo;
    ngx_http_geo_conf_ctx_t   ctx;
#if (NGX_HAVE_INET6)
    static struct in6_addr    zero;
#endif
    value = cf->args->elts;
    geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t));
    if (geo == NULL) {
        return NGX_CONF_ERROR;
    }
    name = value[1];
    if (name.data[0] != '$') {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "invalid variable name \"%V\"", &name);
        return NGX_CONF_ERROR;
    }
    name.len--;
    name.data++;
    if (cf->args->nelts == 3) {
        geo->index = ngx_http_get_variable_index(cf, &name);
        if (geo->index == NGX_ERROR) {
            return NGX_CONF_ERROR;
        }
        name = value[2];
        if (name.data[0] != '$') {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid variable name \"%V\"", &name);
            return NGX_CONF_ERROR;
        }
        name.len--;
        name.data++;
    } else {
        geo->index = -1;
    }
    var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
    if (var == NULL) {
        return NGX_CONF_ERROR;
    }
    pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
    if (pool == NULL) {
        return NGX_CONF_ERROR;
    }
    ngx_memzero(&ctx, sizeof(ngx_http_geo_conf_ctx_t));
    ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
    if (ctx.temp_pool == NULL) {
        return NGX_CONF_ERROR;
    }
    ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value);
    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;
    cf->ctx = &ctx;
    cf->handler = ngx_http_geo;
    cf->handler_conf = conf;
    rv = ngx_conf_parse(cf, NULL);
    *cf = save;
    geo->proxies = ctx.proxies;
    geo->proxy_recursive = ctx.proxy_recursive;
    if (ctx.ranges) {
        if (ctx.high.low && !ctx.binary_include) {
            for (i = 0; i < 0x10000; i++) {
                a = (ngx_array_t *) ctx.high.low[i];
                if (a == NULL || a->nelts == 0) {
                    continue;
                }
                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;
                }
                ngx_memcpy(ctx.high.low[i], a->elts, len);
                ctx.high.low[i][a->nelts].value = NULL;
                ctx.data_size += len + sizeof(void *);
            }
            if (ctx.allow_binary_include
                && !ctx.outside_entries
                && ctx.entries > 100000
                && ctx.includes == 1)
            {
                ngx_http_geo_create_binary_base(&ctx);
            }
        }
        if (ctx.high.default_value == NULL) {
            ctx.high.default_value = &ngx_http_variable_null_value;
        }
        geo->u.high = ctx.high;
        var->get_handler = ngx_http_geo_range_variable;
        var->data = (uintptr_t) geo;
        ngx_destroy_pool(ctx.temp_pool);
        ngx_destroy_pool(pool);
    } else {
        if (ctx.tree == NULL) {
            ctx.tree = ngx_radix_tree_create(cf->pool, -1);
            if (ctx.tree == NULL) {
                return NGX_CONF_ERROR;
            }
        }
        geo->u.trees.tree = ctx.tree;
#if (NGX_HAVE_INET6)
        if (ctx.tree6 == NULL) {
            ctx.tree6 = ngx_radix_tree_create(cf->pool, -1);
            if (ctx.tree6 == NULL) {
                return NGX_CONF_ERROR;
            }
        }
        geo->u.trees.tree6 = ctx.tree6;
#endif
        var->get_handler = ngx_http_geo_cidr_variable;
        var->data = (uintptr_t) geo;
        ngx_destroy_pool(ctx.temp_pool);
        ngx_destroy_pool(pool);
        if (ngx_radix32tree_insert(ctx.tree, 0, 0,
                                   (uintptr_t) &ngx_http_variable_null_value)
            == NGX_ERROR)
        {
            return NGX_CONF_ERROR;
        }
        
#if (NGX_HAVE_INET6)
        if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr,
                                    (uintptr_t) &ngx_http_variable_null_value)
            == NGX_ERROR)
        {
            return NGX_CONF_ERROR;
        }
#endif
    }
    return rv;
}
static char *
ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{
    char                     *rv;
    ngx_str_t                *value;
    ngx_cidr_t                cidr;
    ngx_http_geo_conf_ctx_t  *ctx;
    ctx = cf->ctx;
    value = cf->args->elts;
    if (cf->args->nelts == 1) {
        if (ngx_strcmp(value[0].data, "ranges") == 0) {
            if (ctx->tree
#if (NGX_HAVE_INET6)
                || ctx->tree6
#endif
               )
            {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "the \"ranges\" directive must be "
                                   "the first directive inside \"geo\" block");
                goto failed;
            }
            ctx->ranges = 1;
            rv = NGX_CONF_OK;
            goto done;
        }
        else if (ngx_strcmp(value[0].data, "proxy_recursive") == 0) {
            ctx->proxy_recursive = 1;
            rv = NGX_CONF_OK;
            goto done;
        }
    }
    if (cf->args->nelts != 2) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "invalid number of the geo parameters");
        goto failed;
    }
    if (ngx_strcmp(value[0].data, "include") == 0) {
        rv = ngx_http_geo_include(cf, ctx, &value[1]);
        goto done;
    } else if (ngx_strcmp(value[0].data, "proxy") == 0) {
        if (ngx_http_geo_cidr_value(cf, &value[1], &cidr) != NGX_OK) {
            goto failed;
        }
        rv = ngx_http_geo_add_proxy(cf, ctx, &cidr);
        goto done;
    }
    if (ctx->ranges) {
        rv = ngx_http_geo_range(cf, ctx, value);
    } else {
        rv = ngx_http_geo_cidr(cf, ctx, value);
    }
done:
    ngx_reset_pool(cf->pool);
    return rv;
failed:
    ngx_reset_pool(cf->pool);
    return NGX_CONF_ERROR;
}
static char *
ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    ngx_str_t *value)
{
    u_char      *p, *last;
    in_addr_t    start, end;
    ngx_str_t   *net;
    ngx_uint_t   del;
    if (ngx_strcmp(value[0].data, "default") == 0) {
        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);
        }
        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\" cannot 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;
    } else {
        net = &value[0];
        del = 0;
    }
    last = net->data + net->len;
    p = ngx_strlchr(net->data, last, '-');
    if (p == NULL) {
        goto invalid;
    }
    start = ngx_inet_addr(net->data, p - net->data);
    if (start == INADDR_NONE) {
        goto invalid;
    }
    start = ntohl(start);
    p++;
    end = ngx_inet_addr(p, last - p);
    if (end == INADDR_NONE) {
        goto invalid;
    }
    end = ntohl(end);
    if (start > end) {
        goto invalid;
    }
    if (del) {
        if (ngx_http_geo_delete_range(cf, ctx, start, end)) {
            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                               "no address range \"%V\" to delete", net);
        }
        return NGX_CONF_OK;
    }
    ctx->value = ngx_http_geo_value(cf, ctx, &value[1]);
    if (ctx->value == NULL) {
        return NGX_CONF_ERROR;
    }
    ctx->net = net;
    return ngx_http_geo_add_range(cf, ctx, start, end);
invalid:
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);
    return NGX_CONF_ERROR;
}
static char *
ngx_http_geo_add_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    in_addr_t start, in_addr_t end)
{
    in_addr_t              n;
    ngx_uint_t             h, i, s, e;
    ngx_array_t           *a;
    ngx_http_geo_range_t  *range;
    for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
        h = n >> 16;
        if (n == start) {
            s = n & 0xffff;
        } else {
            s = 0;
        }
        if ((n | 0xffff) > end) {
            e = end & 0xffff;
        } else {
            e = 0xffff;
        }
        a = (ngx_array_t *) ctx->high.low[h];
        if (a == NULL) {
            a = ngx_array_create(ctx->temp_pool, 64,
                                 sizeof(ngx_http_geo_range_t));
            if (a == NULL) {
                return NGX_CONF_ERROR;
            }
            ctx->high.low[h] = (ngx_http_geo_range_t *) a;
        }
        i = a->nelts;
        range = a->elts;
        while (i) {
            i--;
            if (e < (ngx_uint_t) range[i].start) {
                continue;
            }
            if (s > (ngx_uint_t) range[i].end) {
                
                range = ngx_array_push(a);
                if (range == NULL) {
                    return NGX_CONF_ERROR;
                }
                range = a->elts;
                ngx_memmove(&range[i + 2], &range[i + 1],
                           (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
                range[i + 1].start = (u_short) s;
                range[i + 1].end = (u_short) e;
                range[i + 1].value = ctx->value;
                goto next;
            }
            if (s == (ngx_uint_t) range[i].start
                && e == (ngx_uint_t) range[i].end)
            {
                ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                    "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
                    ctx->net, ctx->value, range[i].value);
                range[i].value = ctx->value;
                goto next;
            }
            if (s > (ngx_uint_t) range[i].start
                && e < (ngx_uint_t) range[i].end)
            {
                
                range = ngx_array_push(a);
                if (range == NULL) {
                    return NGX_CONF_ERROR;
                }
                range = ngx_array_push(a);
                if (range == NULL) {
                    return NGX_CONF_ERROR;
                }
                range = a->elts;
                ngx_memmove(&range[i + 3], &range[i + 1],
                           (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t));
                range[i + 2].start = (u_short) (e + 1);
                range[i + 2].end = range[i].end;
                range[i + 2].value = range[i].value;
                range[i + 1].start = (u_short) s;
                range[i + 1].end = (u_short) e;
                range[i + 1].value = ctx->value;
                range[i].end = (u_short) (s - 1);
                goto next;
            }
            if (s == (ngx_uint_t) range[i].start
                && e < (ngx_uint_t) range[i].end)
            {
                
                range = ngx_array_push(a);
                if (range == NULL) {
                    return NGX_CONF_ERROR;
                }
                range = a->elts;
                ngx_memmove(&range[i + 1], &range[i],
                           (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
                range[i + 1].start = (u_short) (e + 1);
                range[i].start = (u_short) s;
                range[i].end = (u_short) e;
                range[i].value = ctx->value;
                goto next;
            }
            if (s > (ngx_uint_t) range[i].start
                && e == (ngx_uint_t) range[i].end)
            {
                
                range = ngx_array_push(a);
                if (range == NULL) {
                    return NGX_CONF_ERROR;
                }
                range = a->elts;
                ngx_memmove(&range[i + 2], &range[i + 1],
                           (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
                range[i + 1].start = (u_short) s;
                range[i + 1].end = (u_short) e;
                range[i + 1].value = ctx->value;
                range[i].end = (u_short) (s - 1);
                goto next;
            }
            s = (ngx_uint_t) range[i].start;
            e = (ngx_uint_t) range[i].end;
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                         "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"",
                         ctx->net,
                         h >> 8, h & 0xff, s >> 8, s & 0xff,
                         h >> 8, h & 0xff, e >> 8, e & 0xff);
            return NGX_CONF_ERROR;
        }
        
        range = ngx_array_push(a);
        if (range == NULL) {
            return NGX_CONF_ERROR;
        }
        range->start = (u_short) s;
        range->end = (u_short) e;
        range->value = ctx->value;
    next:
        continue;
    }
    return NGX_CONF_OK;
}
static ngx_uint_t
ngx_http_geo_delete_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    in_addr_t start, in_addr_t end)
{
    in_addr_t              n;
    ngx_uint_t             h, i, s, e, warn;
    ngx_array_t           *a;
    ngx_http_geo_range_t  *range;
    warn = 0;
    for (n = start; n <= end; n += 0x10000) {
        h = n >> 16;
        if (n == start) {
            s = n & 0xffff;
        } else {
            s = 0;
        }
        if ((n | 0xffff) > end) {
            e = end & 0xffff;
        } else {
            e = 0xffff;
        }
        a = (ngx_array_t *) ctx->high.low[h];
        if (a == NULL) {
            warn = 1;
            continue;
        }
        range = a->elts;
        for (i = 0; i < a->nelts; i++) {
            if (s == (ngx_uint_t) range[i].start
                && e == (ngx_uint_t) range[i].end)
            {
                ngx_memmove(&range[i], &range[i + 1],
                           (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
                a->nelts--;
                break;
            }
            if (s != (ngx_uint_t) range[i].start
                && e != (ngx_uint_t) range[i].end)
            {
                continue;
            }
            warn = 1;
        }
    }
    return warn;
}
static char *
ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    ngx_str_t *value)
{
    char        *rv;
    ngx_int_t    rc, del;
    ngx_str_t   *net;
    ngx_cidr_t   cidr;
    if (ctx->tree == NULL) {
        ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
        if (ctx->tree == NULL) {
            return NGX_CONF_ERROR;
        }
    }
#if (NGX_HAVE_INET6)
    if (ctx->tree6 == NULL) {
        ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1);
        if (ctx->tree6 == NULL) {
            return NGX_CONF_ERROR;
        }
    }
#endif
    if (ngx_strcmp(value[0].data, "default") == 0) {
        cidr.family = AF_INET;
        cidr.u.in.addr = 0;
        cidr.u.in.mask = 0;
        rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
        if (rv != NGX_CONF_OK) {
            return rv;
        }
#if (NGX_HAVE_INET6)
        cidr.family = AF_INET6;
        ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t));
        rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
        if (rv != NGX_CONF_OK) {
            return rv;
        }
#endif
        return NGX_CONF_OK;
    }
    if (ngx_strcmp(value[0].data, "delete") == 0) {
        net = &value[1];
        del = 1;
    } else {
        net = &value[0];
        del = 0;
    }
    if (ngx_http_geo_cidr_value(cf, net, &cidr) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
    if (cidr.family == AF_INET) {
        cidr.u.in.addr = ntohl(cidr.u.in.addr);
        cidr.u.in.mask = ntohl(cidr.u.in.mask);
    }
    if (del) {
        switch (cidr.family) {
#if (NGX_HAVE_INET6)
        case AF_INET6:
            rc = ngx_radix128tree_delete(ctx->tree6,
                                         cidr.u.in6.addr.s6_addr,
                                         cidr.u.in6.mask.s6_addr);
            break;
#endif
        default: 
            rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
                                        cidr.u.in.mask);
            break;
        }
        if (rc != NGX_OK) {
            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                               "no network \"%V\" to delete", net);
        }
        return NGX_CONF_OK;
    }
    return ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], net);
}
static char *
ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net)
{
    ngx_int_t                   rc;
    ngx_http_variable_value_t  *val, *old;
    val = ngx_http_geo_value(cf, ctx, value);
    if (val == NULL) {
        return NGX_CONF_ERROR;
    }
    switch (cidr->family) {
#if (NGX_HAVE_INET6)
    case AF_INET6:
        rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
                                     cidr->u.in6.mask.s6_addr,
                                     (uintptr_t) val);
        if (rc == NGX_OK) {
            return NGX_CONF_OK;
        }
        if (rc == NGX_ERROR) {
            return NGX_CONF_ERROR;
        }
        
        old = (ngx_http_variable_value_t *)
                   ngx_radix128tree_find(ctx->tree6,
                                         cidr->u.in6.addr.s6_addr);
        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
              "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
              net, val, old);
        rc = ngx_radix128tree_delete(ctx->tree6,
                                     cidr->u.in6.addr.s6_addr,
                                     cidr->u.in6.mask.s6_addr);
        if (rc == NGX_ERROR) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
            return NGX_CONF_ERROR;
        }
        rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
                                     cidr->u.in6.mask.s6_addr,
                                     (uintptr_t) val);
        break;
#endif
    default: 
        rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
                                    cidr->u.in.mask, (uintptr_t) val);
        if (rc == NGX_OK) {
            return NGX_CONF_OK;
        }
        if (rc == NGX_ERROR) {
            return NGX_CONF_ERROR;
        }
        
        old = (ngx_http_variable_value_t *)
                   ngx_radix32tree_find(ctx->tree, cidr->u.in.addr);
        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
              "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
              net, val, old);
        rc = ngx_radix32tree_delete(ctx->tree,
                                    cidr->u.in.addr, cidr->u.in.mask);
        if (rc == NGX_ERROR) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
            return NGX_CONF_ERROR;
        }
        rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
                                    cidr->u.in.mask, (uintptr_t) val);
        break;
    }
    if (rc == NGX_OK) {
        return NGX_CONF_OK;
    }
    return NGX_CONF_ERROR;
}
static ngx_http_variable_value_t *
ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    ngx_str_t *value)
{
    uint32_t                             hash;
    ngx_http_variable_value_t           *val;
    ngx_http_geo_variable_value_node_t  *gvvn;
    hash = ngx_crc32_long(value->data, value->len);
    gvvn = (ngx_http_geo_variable_value_node_t *)
               ngx_str_rbtree_lookup(&ctx->rbtree, value, hash);
    if (gvvn) {
        return gvvn->value;
    }
    val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t));
    if (val == NULL) {
        return NULL;
    }
    val->len = value->len;
    val->data = ngx_pstrdup(ctx->pool, value);
    if (val->data == NULL) {
        return NULL;
    }
    val->valid = 1;
    val->no_cacheable = 0;
    val->not_found = 0;
    gvvn = ngx_palloc(ctx->temp_pool,
                      sizeof(ngx_http_geo_variable_value_node_t));
    if (gvvn == NULL) {
        return NULL;
    }
    gvvn->sn.node.key = hash;
    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;
}
static char *
ngx_http_geo_add_proxy(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
    ngx_cidr_t *cidr)
{
    ngx_cidr_t  *c;
    if (ctx->proxies == NULL) {
        ctx->proxies = ngx_array_create(ctx->pool, 4, sizeof(ngx_cidr_t));
        if (ctx->proxies == NULL) {
            return NGX_CONF_ERROR;
        }
    }
    c = ngx_array_push(ctx->proxies);
    if (c == NULL) {
        return NGX_CONF_ERROR;
    }
    *c = *cidr;
    return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr)
{
    ngx_int_t  rc;
    if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
        cidr->family = AF_INET;
        cidr->u.in.addr = 0xffffffff;
        cidr->u.in.mask = 0xffffffff;
        return NGX_OK;
    }
    rc = ngx_ptocidr(net, cidr);
    if (rc == NGX_ERROR) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net);
        return NGX_ERROR;
    }
    if (rc == NGX_DONE) {
        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                           "low address bits of %V are meaningless", net);
    }
    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;
    }
    if (ctx->ranges) {
        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, ch;
    time_t                      mtime;
    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\" cannot be mixed with usual entries",
            name->data);
        rc = NGX_ERROR;
        goto done;
    }
    if (ctx->binary_include) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
            "second binary geo range base \"%s\" cannot be mixed with \"%s\"",
            name->data, ctx->include_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);
    mtime = ngx_file_mtime(&fi);
    ch = name->data[name->len - 4];
    name->data[name->len - 4] = '\0';
    if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
                           ngx_file_info_n " \"%s\" failed", name->data);
        goto failed;
    }
    name->data[name->len - 4] = ch;
    if (mtime < ngx_file_mtime(&fi)) {
        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                           "stale binary geo range base \"%s\"", name->data);
        goto failed;
    }
    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 (size < 16 || 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);
}