diff --git a/.gitignore b/.gitignore index 0743c23..33056e3 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,7 @@ target #erlang /binding/erlang/_build /binding/erlang/doc + +#vscode +.vscode +build diff --git a/binding/c/Makefile b/binding/c/Makefile index e444283..ea9523b 100644 --- a/binding/c/Makefile +++ b/binding/c/Makefile @@ -6,9 +6,19 @@ xdb_searcher: xdb_searcher.h xdb_searcher.c main.c util_test: xdb_searcher.h xdb_searcher.c util_test.c gcc -g -O2 -I./ xdb_searcher.c util_test.c -o util_test +xdb_searcher.o: xdb_searcher.c + gcc -c -o xdb_searcher.o xdb_searcher.c + +xdb_searcher_lib: xdb_searcher.o + mkdir -p build/lib + mkdir -p build/include + ar -rc build/lib/libxdb_searcher.a `find . -name *.o` + cp xdb_searcher.h build/include + clean: find ./ -name \*.o | xargs rm -f find ./ -name util_test | xargs rm -f find ./ -name xdb_searcher | xargs rm -f + rm -rf build .PHONY: all clean xdb_searcher util_test \ No newline at end of file diff --git a/binding/c/xdb_searcher.h b/binding/c/xdb_searcher.h index 12808bc..8be9429 100644 --- a/binding/c/xdb_searcher.h +++ b/binding/c/xdb_searcher.h @@ -17,7 +17,7 @@ # define XDB_PUBLIC(type) extern __declspec(dllexport) type # define XDB_PRIVATE(type) static type # define XDB_WINDOWS -#elif ( defined(linux) || defined(_UNIX) ) +#elif ( defined(linux) || defined(_UNIX) || defined(__APPLE__) ) # define XDB_PUBLIC(type) extern type # define XDB_PRIVATE(type) static inline type # define XDB_LINUX diff --git a/binding/nginx/README.md b/binding/nginx/README.md new file mode 100644 index 0000000..e71f9c0 --- /dev/null +++ b/binding/nginx/README.md @@ -0,0 +1,72 @@ +# nginx-ip2region + +## build + +```shell +$ mkdir -p workspace +$ cd workspace +$ wget https://nginx.org/download/nginx-1.23.4.tar.gz +$ tar -zxf nginx-1.23.4.tar.gz && rm -rf nginx-1.23.4.tar.gz +$ git clone https://github.com/lionsoul2014/ip2region.git +$ cd ip2region/binding/c +$ make xdb_searcher_lib +$ cd ../../../nginx-1.23.4 +$ ./configure \ + --add-module=$(PWD)/../ip2region/binding/nginx \ + --with-cc-opt="-I $(PWD)/../ip2region/binding/c/build/include" \ + --with-ld-opt="-L$(PWD)/../ip2region/binding/c/build/lib" +$ make +$ make install +``` + +## nginx conf + +> Syntax: `ip2region_db xdb_file_path [cache_policy Optional]`; +> Context: http + +cache_policy: `file` or `vectorIndex` or `content`, default: `content` + +Edit `nginx.conf` add `ip2region_db` directive + +```nginx +... +http { + + log_format main escape=json '{' + '"remote_addr": "$remote_addr", ' + '"region": "$ip2region", ' + '"http_x_forwarded_for": "$http_x_forwarded_for"' + '}'; + + access_log logs/access.log main; + + # set xdb file path + ip2region_db ip2region.xdb; + # ip2region_db ip2region.xdb vectorIndex; + # ip2region_db ip2region.xdb file; + # ip2region_db ip2region.xdb content; + + server { + listen 80; + server_name localhost; + + location / { + root html; + index index.html index.htm; + } + } +} + +``` + +Copy `ip2region.xdb` to `nginx/config` folder, then restart nginx, the `region` data stored in `ip2region` variable + +nginx access log sample + +```log +{"remote_addr": "127.0.0.1", "region": "0|0|0|内网IP|内网IP", "http_x_forwarded_for": ""} +{"remote_addr": "127.0.0.1", "region": "0|0|0|内网IP|内网IP", "http_x_forwarded_for": ""} + +``` + +Made with ♥ by Wu Jian Ping diff --git a/binding/nginx/config b/binding/nginx/config new file mode 100644 index 0000000..1da0253 --- /dev/null +++ b/binding/nginx/config @@ -0,0 +1,22 @@ +ngx_addon_name=ngx_http_ip2region_module + +NGX_HTTP_IP2REGION_SRCS=" \ + $ngx_addon_dir/src/ngx_http_ip2region_module.c \ + " + +NGX_HTTP_IP2REGION_DEPS=" \ + " + +if test -n "$ngx_module_link"; then + ngx_module_type=HTTP + ngx_module_name=$ngx_addon_name + ngx_module_deps="$NGX_HTTP_IP2REGION_DEPS" + ngx_module_srcs="$NGX_HTTP_IP2REGION_SRCS" + ngx_module_libs="-lxdb_searcher" + + . auto/module +else + HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" + NGX_ADDON_DEPS="$NGX_ADDON_DEPS $NGX_HTTP_IP2REGION_DEPS" + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $NGX_HTTP_IP2REGION_SRCS" +fi \ No newline at end of file diff --git a/binding/nginx/src/ngx_http_ip2region_module.c b/binding/nginx/src/ngx_http_ip2region_module.c new file mode 100644 index 0000000..aa3e3e7 --- /dev/null +++ b/binding/nginx/src/ngx_http_ip2region_module.c @@ -0,0 +1,345 @@ +/* + * Created by Wu Jian Ping on - 2023/03/30. + */ + +#include "ngx_http_ip2region_module.h" + +static ngx_int_t ngx_http_ua_parser_test_full_name(char *name); + +static ngx_int_t ngx_http_ip2region_add_variables(ngx_conf_t *cf); + +static void *ngx_http_ip2region_create_conf(ngx_conf_t *cf); +static void ngx_http_ip2region_cleanup(void *data); +static char *ngx_http_ip2region_init(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + +static ngx_int_t ngx_http_ip2region_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_http_module_t ngx_http_ip2region_ctx = { + ngx_http_ip2region_add_variables, /* pre configuration */ + NULL, /* post configuration */ + ngx_http_ip2region_create_conf, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +static ngx_command_t ngx_http_ip2region_commands[] = { + { ngx_string("ip2region_db"), + NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE12, + ngx_http_ip2region_init, + NGX_HTTP_MAIN_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +/* ngx_module_t is required, otherwise failed at complie time */ +ngx_module_t ngx_http_ip2region_module = { + NGX_MODULE_V1, + &ngx_http_ip2region_ctx, /* module context */ + ngx_http_ip2region_commands, /* module directives */ + NGX_HTTP_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 +}; + + +static ngx_http_variable_t ngx_http_ip2region_vars[] = { + { ngx_string("ip2region"), NULL, + ngx_http_ip2region_variable, + 0, 0, 0 }, + + ngx_http_null_variable +}; + + +static char * +ngx_http_ip2region_init(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_ip2region_conf_t *ip2region_cf; + ip2region_searcher_t *ip2region_searcher; + char *db_name, *cache_policy; + ngx_str_t *value; + int err; + char *db_path; + size_t len; + ip2region_cf = conf; + + if (ip2region_cf->ip2region_searcher) { + return "is duplicate"; + } + + value = cf->args->elts; + + db_name = (char *)value[1].data; + + // default cache_policy: content + if(cf->args->nelts == 2) { + cache_policy = "content"; + } else { + cache_policy = (char *)value[2].data; + } + + if(ngx_http_ua_parser_test_full_name(db_name) == NGX_OK) { + db_path = db_name; + } else { // relative path to conf directory + len = ngx_cycle->conf_prefix.len + strlen(db_name) + 1; + db_path = malloc(len); + memset(db_path, '\0', len); + memcpy(db_path, ngx_cycle->conf_prefix.data, ngx_cycle->conf_prefix.len); + strcat(db_path, db_name); + } + + ip2region_searcher = ngx_palloc(cf->pool, sizeof(ip2region_searcher_t)); + + if(ip2region_searcher == NULL) { + return NGX_CONF_ERROR; + } + + ip2region_searcher->v_index = NULL; + ip2region_searcher->c_buffer = NULL; + + if (strcmp(cache_policy, "file") == 0) { + err = xdb_new_with_file_only(&ip2region_searcher->searcher, db_path); + if (err != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "failed to create searcher: %s", db_path); + return NGX_CONF_ERROR; + } + } else if (strcmp(cache_policy, "vectorIndex") == 0) { + ip2region_searcher->v_index = xdb_load_vector_index_from_file(db_path); + if (ip2region_searcher->v_index == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "failed to load vector index from: %s", db_path); + return NGX_CONF_ERROR; + } + + err = xdb_new_with_vector_index(&ip2region_searcher->searcher, db_path, ip2region_searcher->v_index); + if (err != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "failed to create vector index cached searcher: %s", db_path); + return NGX_CONF_ERROR; + } + } else if (strcmp(cache_policy, "content") == 0) { + ip2region_searcher->c_buffer = xdb_load_content_from_file(db_path); + if (ip2region_searcher->c_buffer == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "failed to load xdb content: %s", db_path); + return NGX_CONF_ERROR; + } + + err = xdb_new_with_buffer(&ip2region_searcher->searcher, ip2region_searcher->c_buffer); + if (err != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "failed to create content cached searcher: %s", db_path); + return NGX_CONF_ERROR; + } + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid cache policy `%V`, options: file/vectorIndex/content", &value[2]); + return NGX_CONF_ERROR; + } + + ip2region_cf->ip2region_searcher = ip2region_searcher; + + if(ngx_http_ua_parser_test_full_name(db_name) != NGX_OK) { + free(db_path); + } + + return NGX_CONF_OK; +} + + +static void * +ngx_http_ip2region_create_conf(ngx_conf_t *cf) +{ + ngx_pool_cleanup_t *cln; + ngx_http_ip2region_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ip2region_conf_t)); + if (conf == NULL) { + return NULL; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_http_ip2region_cleanup; + cln->data = conf; + + return conf; +} + + +static ngx_int_t +ngx_http_ip2region_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var; + ngx_http_variable_t *v; + + for (v = ngx_http_ip2region_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + return NGX_OK; +} + + +static ngx_int_t +ngx_http_ip2region_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_ip2region_conf_t *ip2region_conf; + struct sockaddr_in *sin; + char region[512] = {'\0'}; + int err; + unsigned int ip; + +#if (NGX_HAVE_INET6) + u_char *p; + in_addr_t addr; + struct sockaddr_in6 *sin6; +#endif + + ip2region_conf = ngx_http_get_module_main_conf(r, ngx_http_ip2region_module); + + if (ip2region_conf->ip2region_searcher != NULL) { + + switch (r->connection->sockaddr->sa_family) { + case AF_INET: + sin = (struct sockaddr_in *) r->connection->sockaddr; + ip = htonl(sin->sin_addr.s_addr); + err = xdb_search(&ip2region_conf->ip2region_searcher->searcher, ip, region, sizeof(region)); + if (err == 0) { + v->data = (unsigned char *)region; + v->len = strlen(region); + return NGX_OK; + } + break; + +#if (NGX_HAVE_INET6) + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) r->connection->sockaddr; + p = sin6->sin6_addr.s6_addr; + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + addr = p[12] << 24; + addr += p[13] << 16; + addr += p[14] << 8; + addr += p[15]; + + ip = htonl(addr); + err = xdb_search(&ip2region_conf->ip2region_searcher->searcher, ip, region, sizeof(region)); + if (err == 0) { + v->data = (unsigned char *)region; + v->len = strlen(region); + return NGX_OK; + } + } + break; + +#endif + + } + } + + v->not_found = 1; + return NGX_OK; +} + + +static void +ngx_http_ip2region_cleanup(void *data) +{ + ngx_http_ip2region_conf_t *ip2region_conf = data; + + if(ip2region_conf->ip2region_searcher != NULL) { + xdb_close(&ip2region_conf->ip2region_searcher->searcher); + + // check and free the vector index + if (ip2region_conf->ip2region_searcher->v_index != NULL) { + xdb_close_vector_index(ip2region_conf->ip2region_searcher->v_index); + ip2region_conf->ip2region_searcher->v_index = NULL; + } + + // check and free the content buffer + if (ip2region_conf->ip2region_searcher->c_buffer != NULL) { + xdb_close_content(ip2region_conf->ip2region_searcher->c_buffer); + ip2region_conf->ip2region_searcher->c_buffer = NULL; + } + + ip2region_conf->ip2region_searcher = NULL; + } +} + + +static ngx_int_t +ngx_http_ua_parser_test_full_name(char *name) +{ +#if (NGX_WIN32) + u_char c0, c1; + + c0 = name[0]; + + if (strlen(name) < 2) { + if (c0 == '/') { + return 2; + } + + return NGX_DECLINED; + } + + c1 = name[1]; + + if (c1 == ':') { + c0 |= 0x20; + + if ((c0 >= 'a' && c0 <= 'z')) { + return NGX_OK; + } + + return NGX_DECLINED; + } + + if (c1 == '/') { + return NGX_OK; + } + + if (c0 == '/') { + return 2; + } + + return NGX_DECLINED; + +#else + + if (name[0] == '/') { + return NGX_OK; + } + + return NGX_DECLINED; + +#endif +} diff --git a/binding/nginx/src/ngx_http_ip2region_module.h b/binding/nginx/src/ngx_http_ip2region_module.h new file mode 100644 index 0000000..8861cee --- /dev/null +++ b/binding/nginx/src/ngx_http_ip2region_module.h @@ -0,0 +1,23 @@ +/* + * Created by Wu Jian Ping on - 2023/03/30. + */ + +#ifndef __NGX_HTTP_IP2REGION_MODULE_H_INCLUDED__ +#define __NGX_HTTP_IP2REGION_MODULE_H_INCLUDED__ + +#include +#include +#include +#include + +typedef struct { + xdb_searcher_t searcher; + xdb_vector_index_t *v_index; + xdb_content_t *c_buffer; +} ip2region_searcher_t; + +typedef struct { + ip2region_searcher_t *ip2region_searcher; +} ngx_http_ip2region_conf_t; + +#endif diff --git a/binding/nginx/t/http_ip2region.t b/binding/nginx/t/http_ip2region.t new file mode 100644 index 0000000..7b16856 --- /dev/null +++ b/binding/nginx/t/http_ip2region.t @@ -0,0 +1,27 @@ +use lib 'lib'; +use Test::Nginx::Socket; # 'no_plan'; + +repeat_each(2); + +plan tests => repeat_each() * 124; + +no_long_string(); +#no_diff; + +run_tests(); + +__DATA__ + +=== TEST 1: set request header at client side +--- config + location /foo { + echo $http_x_foo; + } +--- request + GET /foo +--- more_headers +X-Foo: blah +--- response_headers +! X-Foo +--- response_body +blah