[nginx] Stream: geo module.

Vladimir Homutov vl at nginx.com
Tue Jul 12 14:37:59 UTC 2016


details:   http://hg.nginx.org/nginx/rev/80875b75d27e
branches:  
changeset: 6631:80875b75d27e
user:      Vladimir Homutov <vl at nginx.com>
date:      Thu Jun 30 16:12:50 2016 +0300
description:
Stream: geo module.

diffstat:

 auto/modules                       |    10 +
 auto/options                       |     3 +
 src/stream/ngx_stream_geo_module.c |  1572 ++++++++++++++++++++++++++++++++++++
 3 files changed, 1585 insertions(+), 0 deletions(-)

diffs (truncated from 1623 to 1000 lines):

diff -r 558db057adaa -r 80875b75d27e auto/modules
--- a/auto/modules	Tue Jul 12 17:34:43 2016 +0300
+++ b/auto/modules	Thu Jun 30 16:12:50 2016 +0300
@@ -1024,6 +1024,16 @@ if [ $STREAM != NO ]; then
         . auto/module
     fi
 
+    if [ $STREAM_GEO = YES ]; then
+        ngx_module_name=ngx_stream_geo_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_geo_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_GEO
+
+        . auto/module
+    fi
+
     if [ $STREAM_GEOIP != NO ]; then
         ngx_module_name=ngx_stream_geoip_module
         ngx_module_deps=
diff -r 558db057adaa -r 80875b75d27e auto/options
--- a/auto/options	Tue Jul 12 17:34:43 2016 +0300
+++ b/auto/options	Thu Jun 30 16:12:50 2016 +0300
@@ -117,6 +117,7 @@ STREAM=NO
 STREAM_SSL=NO
 STREAM_LIMIT_CONN=YES
 STREAM_ACCESS=YES
+STREAM_GEO=YES
 STREAM_GEOIP=NO
 STREAM_MAP=YES
 STREAM_RETURN=YES
@@ -300,6 +301,7 @@ use the \"--with-mail_ssl_module\" optio
         --without-stream_limit_conn_module)
                                          STREAM_LIMIT_CONN=NO       ;;
         --without-stream_access_module)  STREAM_ACCESS=NO           ;;
+        --without-stream_geo_module)     STREAM_GEO=NO              ;;
         --without-stream_map_module)     STREAM_MAP=NO              ;;
         --without-stream_return_module)  STREAM_RETURN=NO           ;;
         --without-stream_upstream_hash_module)
@@ -502,6 +504,7 @@ cat << END
   --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module
   --without-stream_limit_conn_module disable ngx_stream_limit_conn_module
   --without-stream_access_module     disable ngx_stream_access_module
+  --without-stream_geo_module        disable ngx_stream_geo_module
   --without-stream_map_module        disable ngx_stream_map_module
   --without-stream_return_module     disable ngx_stream_return_module
   --without-stream_upstream_hash_module
diff -r 558db057adaa -r 80875b75d27e src/stream/ngx_stream_geo_module.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/stream/ngx_stream_geo_module.c	Thu Jun 30 16:12:50 2016 +0300
@@ -0,0 +1,1572 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_stream_variable_value_t       *value;
+    u_short                            start;
+    u_short                            end;
+} ngx_stream_geo_range_t;
+
+
+typedef struct {
+    ngx_radix_tree_t                  *tree;
+#if (NGX_HAVE_INET6)
+    ngx_radix_tree_t                  *tree6;
+#endif
+} ngx_stream_geo_trees_t;
+
+
+typedef struct {
+    ngx_stream_geo_range_t           **low;
+    ngx_stream_variable_value_t       *default_value;
+} ngx_stream_geo_high_ranges_t;
+
+
+typedef struct {
+    ngx_str_node_t                     sn;
+    ngx_stream_variable_value_t       *value;
+    size_t                             offset;
+} ngx_stream_geo_variable_value_node_t;
+
+
+typedef struct {
+    ngx_stream_variable_value_t       *value;
+    ngx_str_t                         *net;
+    ngx_stream_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_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_stream_geo_conf_ctx_t;
+
+
+typedef struct {
+    union {
+        ngx_stream_geo_trees_t         trees;
+        ngx_stream_geo_high_ranges_t   high;
+    } u;
+
+    ngx_int_t                          index;
+} ngx_stream_geo_ctx_t;
+
+
+static ngx_int_t ngx_stream_geo_addr(ngx_stream_session_t *s,
+    ngx_stream_geo_ctx_t *ctx, ngx_addr_t *addr);
+
+static char *ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+static char *ngx_stream_geo_range(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
+static char *ngx_stream_geo_add_range(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
+static ngx_uint_t ngx_stream_geo_delete_range(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
+static char *ngx_stream_geo_cidr(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
+static char *ngx_stream_geo_cidr_add(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr, ngx_str_t *value,
+    ngx_str_t *net);
+static ngx_stream_variable_value_t *ngx_stream_geo_value(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
+static ngx_int_t ngx_stream_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net,
+    ngx_cidr_t *cidr);
+static char *ngx_stream_geo_include(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name);
+static ngx_int_t ngx_stream_geo_include_binary_base(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name);
+static void ngx_stream_geo_create_binary_base(ngx_stream_geo_conf_ctx_t *ctx);
+static u_char *ngx_stream_geo_copy_values(u_char *base, u_char *p,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+
+
+static ngx_command_t  ngx_stream_geo_commands[] = {
+
+    { ngx_string("geo"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
+      ngx_stream_geo_block,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_geo_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_geo_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_geo_module_ctx,            /* module context */
+    ngx_stream_geo_commands,               /* module directives */
+    NGX_STREAM_MODULE,                     /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+typedef struct {
+    u_char    GEORNG[6];
+    u_char    version;
+    u_char    ptr_size;
+    uint32_t  endianness;
+    uint32_t  crc32;
+} ngx_stream_geo_header_t;
+
+
+static ngx_stream_geo_header_t  ngx_stream_geo_header = {
+    { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0
+};
+
+
+/* geo range is AF_INET only */
+
+static ngx_int_t
+ngx_stream_geo_cidr_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data;
+
+    in_addr_t                     inaddr;
+    ngx_addr_t                    addr;
+    struct sockaddr_in           *sin;
+    ngx_stream_variable_value_t  *vv;
+#if (NGX_HAVE_INET6)
+    u_char                       *p;
+    struct in6_addr              *inaddr6;
+#endif
+
+    if (ngx_stream_geo_addr(s, ctx, &addr) != NGX_OK) {
+        vv = (ngx_stream_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_stream_variable_value_t *)
+                      ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
+
+        } else {
+            vv = (ngx_stream_variable_value_t *)
+                      ngx_radix128tree_find(ctx->u.trees.tree6, p);
+        }
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) addr.sockaddr;
+        inaddr = ntohl(sin->sin_addr.s_addr);
+
+        vv = (ngx_stream_variable_value_t *)
+                  ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
+
+        break;
+    }
+
+done:
+
+    *v = *vv;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream geo: %v", v);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geo_range_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data;
+
+    in_addr_t                inaddr;
+    ngx_addr_t               addr;
+    ngx_uint_t               n;
+    struct sockaddr_in      *sin;
+    ngx_stream_geo_range_t  *range;
+#if (NGX_HAVE_INET6)
+    u_char                  *p;
+    struct in6_addr         *inaddr6;
+#endif
+
+    *v = *ctx->u.high.default_value;
+
+    if (ngx_stream_geo_addr(s, 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: /* AF_INET */
+            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_STREAM, s->connection->log, 0,
+                   "stream geo: %v", v);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geo_addr(ngx_stream_session_t *s, ngx_stream_geo_ctx_t *ctx,
+    ngx_addr_t *addr)
+{
+    ngx_stream_variable_value_t  *v;
+
+    if (ctx->index == -1) {
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "stream geo started: %V", &s->connection->addr_text);
+
+        addr->sockaddr = s->connection->sockaddr;
+        addr->socklen = s->connection->socklen;
+        /* addr->name = s->connection->addr_text; */
+
+        return NGX_OK;
+    }
+
+    v = ngx_stream_get_flushed_variable(s, ctx->index);
+
+    if (v == NULL || v->not_found) {
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "stream geo not found");
+
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream geo started: %v", v);
+
+    if (ngx_parse_addr(s->connection->pool, addr, v->data, v->len) == NGX_OK) {
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
+
+static char *
+ngx_stream_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_stream_variable_t        *var;
+    ngx_stream_geo_ctx_t         *geo;
+    ngx_stream_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_stream_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_stream_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_stream_add_variable(cf, &name, NGX_STREAM_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_stream_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_stream_geo_header_t)
+                  + sizeof(ngx_stream_variable_value_t)
+                  + 0x10000 * sizeof(ngx_stream_geo_range_t *);
+    ctx.allow_binary_include = 1;
+
+    save = *cf;
+    cf->pool = pool;
+    cf->ctx = &ctx;
+    cf->handler = ngx_stream_geo;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    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_stream_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_stream_geo_create_binary_base(&ctx);
+            }
+        }
+
+        if (ctx.high.default_value == NULL) {
+            ctx.high.default_value = &ngx_stream_variable_null_value;
+        }
+
+        geo->u.high = ctx.high;
+
+        var->get_handler = ngx_stream_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_stream_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_stream_variable_null_value)
+            == NGX_ERROR)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+        /* NGX_BUSY is okay (default was set explicitly) */
+
+#if (NGX_HAVE_INET6)
+        if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr,
+                                    (uintptr_t) &ngx_stream_variable_null_value)
+            == NGX_ERROR)
+        {
+            return NGX_CONF_ERROR;
+        }
+#endif
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    char                       *rv;
+    ngx_str_t                  *value;
+    ngx_stream_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;
+        }
+    }
+
+    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_stream_geo_include(cf, ctx, &value[1]);
+
+        goto done;
+    }
+
+    if (ctx->ranges) {
+        rv = ngx_stream_geo_range(cf, ctx, value);
+
+    } else {
+        rv = ngx_stream_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_stream_geo_range(ngx_conf_t *cf, ngx_stream_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_stream_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_stream_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_stream_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_stream_geo_value(cf, ctx, &value[1]);
+
+    if (ctx->value == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ctx->net = net;
+
+    return ngx_stream_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;
+}
+
+
+/* the add procedure is optimized to add a growing up sequence */
+
+static char *
+ngx_stream_geo_add_range(ngx_conf_t *cf, ngx_stream_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_stream_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_stream_geo_range_t));
+            if (a == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->high.low[h] = (ngx_stream_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) {
+
+                /* add after the range */
+
+                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_stream_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)
+            {
+                /* split the range and insert the new one */
+
+                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_stream_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)
+            {
+                /* shift the range start and insert the new range */
+
+                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_stream_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)
+            {
+                /* shift the range end and insert the new range */
+
+                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_stream_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;
+        }
+
+        /* add the first range */
+
+        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_stream_geo_delete_range(ngx_conf_t *cf, ngx_stream_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_stream_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_stream_geo_range_t));
+
+                a->nelts--;
+
+                break;
+            }
+



More information about the nginx-devel mailing list