Skip to content

Commit

Permalink
✨ IPV6 support.
Browse files Browse the repository at this point in the history
🐛 Fixed a bug that incorrectly identified IPV4 network segments.
  • Loading branch information
ADD-SP committed Dec 10, 2020
1 parent 1a8ca7b commit 73a22eb
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 116 deletions.
81 changes: 43 additions & 38 deletions inc/ngx_http_waf_module_check.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <ngx_inet.h>
#include "ngx_http_waf_module_macro.h"
#include "ngx_http_waf_module_type.h"
#include "ngx_http_waf_module_util.h"


#ifndef NGX_HTTP_WAF_MODLULE_CHECK_H
Expand All @@ -33,23 +34,23 @@ extern ngx_module_t ngx_http_waf_module; /**< 模块详情 */
typedef ngx_int_t (*ngx_http_waf_check)(ngx_http_request_t* r, ngx_int_t* out_http_status);

/**
* @brief 检查客户端 IPV4 地址是否在白名单中。
* @brief 检查客户端 IP 地址是否在白名单中。
* @param[out] out_http_status 当出发规则时需要返回的 HTTP 状态码。
* @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。
* @retval MATCHED IPV4 地址在白名单中。
* @retval NOT_MATCHED IPV4 地址不在白名单中。
* @retval MATCHED IP 地址在白名单中。
* @retval NOT_MATCHED IP 地址不在白名单中。
*/
static ngx_int_t ngx_http_waf_handler_check_white_ipv4(ngx_http_request_t* r, ngx_int_t* out_http_status);
static ngx_int_t ngx_http_waf_handler_check_white_ip(ngx_http_request_t* r, ngx_int_t* out_http_status);


/**
* @brief 检查客户端 IPV4 地址是否在黑名单中。
* @brief 检查客户端 IP 地址是否在黑名单中。
* @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。
* @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。
* @retval MATCHED IPV4 地址在黑名单中。
* @retval NOT_MATCHED IPV4 地址不在黑名单中。
* @retval MATCHED IP 地址在黑名单中。
* @retval NOT_MATCHED IP 地址不在黑名单中。
*/
static ngx_int_t ngx_http_waf_handler_check_black_ipv4(ngx_http_request_t* r, ngx_int_t* out_http_status);
static ngx_int_t ngx_http_waf_handler_check_black_ip(ngx_http_request_t* r, ngx_int_t* out_http_status);


/**
Expand Down Expand Up @@ -132,17 +133,6 @@ static ngx_int_t ngx_http_waf_handler_check_black_referer(ngx_http_request_t* r,
static ngx_int_t ngx_http_waf_handler_check_black_cookie(ngx_http_request_t* r, ngx_int_t* out_http_status);


/**
* @brief 检查两个 IPV4 是否属于同一网段
* @param[in] ip 整型格式的 IPV4
* @param[in] ipv4 格式化后的 IPV4
* @return 如果属于同一网段返回 MATCHED,反之返回 NOT_MATCHED。
* @retval MATCHED 属于同一网段。
* @retval NOT_MATCHED 不属于同一网段。
*/
static ngx_int_t ngx_http_waf_handler_check_ipv4(unsigned long ip, const ipv4_t* ipv4);


/**
* @brief 逐渐释放旧的哈希表所占用的内存
* @li 第一阶段:备份现有的哈希表和现有的内存池,然后创建新的哈希表和内存池。
Expand All @@ -166,56 +156,82 @@ static void check_post(ngx_http_request_t* r);
*/


static ngx_int_t ngx_http_waf_handler_check_white_ipv4(ngx_http_request_t* r, ngx_int_t* out_http_status) {
static ngx_int_t ngx_http_waf_handler_check_white_ip(ngx_http_request_t* r, ngx_int_t* out_http_status) {
ngx_http_waf_ctx_t* ctx = ngx_http_get_module_ctx(r, ngx_http_waf_module);
ngx_http_waf_srv_conf_t* srv_conf = ngx_http_get_module_srv_conf(r, ngx_http_waf_module);
struct sockaddr_in* sin = (struct sockaddr_in*)r->connection->sockaddr;

if (CHECK_FLAG(srv_conf->waf_mode, MODE_INSPECT_IP) == FALSE) {
return NOT_MATCHED;
}

if (r->connection->sockaddr->sa_family == AF_INET) {
unsigned long ipv4 = sin->sin_addr.s_addr;
struct sockaddr_in* sin = (struct sockaddr_in*)r->connection->sockaddr;
uint32_t ipv4 = sin->sin_addr.s_addr;
ipv4_t* p = srv_conf->white_ipv4->elts;
size_t index = 0;
for (; index < srv_conf->white_ipv4->nelts; index++, p++) {
if (ngx_http_waf_handler_check_ipv4(ipv4, p) == MATCHED) {
if (ipv4_netcmp(ipv4, p) == MATCHED) {
ctx->blocked = FALSE;
strcpy((char*)ctx->rule_type, "WHITE-IPV4");
strcpy((char*)ctx->rule_deatils, (char*)p->text);
*out_http_status = NGX_DECLINED;
return MATCHED;
}
}
} else if (r->connection->sockaddr->sa_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)r->connection->sockaddr;
ipv6_t* p = srv_conf->white_ipv6->elts;
size_t index = 0;
for (; index < srv_conf->white_ipv6->nelts; index++, p++) {
if (ipv6_netcmp(sin6->sin6_addr.__in6_u.__u6_addr8, p) == MATCHED) {
ctx->blocked = TRUE;
strcpy((char*)ctx->rule_type, "WHITE-IPV6");
strcpy((char*)ctx->rule_deatils, (char*)p->text);
*out_http_status = NGX_DECLINED;
return MATCHED;
}
}
}

return NOT_MATCHED;
}


static ngx_int_t ngx_http_waf_handler_check_black_ipv4(ngx_http_request_t* r, ngx_int_t* out_http_status) {
static ngx_int_t ngx_http_waf_handler_check_black_ip(ngx_http_request_t* r, ngx_int_t* out_http_status) {
ngx_http_waf_ctx_t* ctx = ngx_http_get_module_ctx(r, ngx_http_waf_module);
ngx_http_waf_srv_conf_t* srv_conf = ngx_http_get_module_srv_conf(r, ngx_http_waf_module);
struct sockaddr_in* sin = (struct sockaddr_in*)r->connection->sockaddr;

if (CHECK_FLAG(srv_conf->waf_mode, MODE_INSPECT_IP) == FALSE) {
return NOT_MATCHED;
}

if (r->connection->sockaddr->sa_family == AF_INET) {
unsigned long ipv4 = sin->sin_addr.s_addr;
struct sockaddr_in* sin = (struct sockaddr_in*)r->connection->sockaddr;
uint32_t ipv4 = sin->sin_addr.s_addr;
ipv4_t* p = srv_conf->black_ipv4->elts;
size_t index = 0;
for (; index < srv_conf->black_ipv4->nelts; index++, p++) {
if (ngx_http_waf_handler_check_ipv4(ipv4, p) == MATCHED) {
if (ipv4_netcmp(ipv4, p) == MATCHED) {
ctx->blocked = TRUE;
strcpy((char*)ctx->rule_type, "BLACK-IPV4");
strcpy((char*)ctx->rule_deatils, (char*)p->text);
*out_http_status = NGX_HTTP_FORBIDDEN;
return MATCHED;
}
}
} else if (r->connection->sockaddr->sa_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)r->connection->sockaddr;
ipv6_t* p = srv_conf->black_ipv6->elts;
size_t index = 0;
for (; index < srv_conf->black_ipv6->nelts; index++, p++) {
if (ipv6_netcmp(sin6->sin6_addr.__in6_u.__u6_addr8, p) == MATCHED) {
ctx->blocked = TRUE;
strcpy((char*)ctx->rule_type, "BLACK-IPV6");
strcpy((char*)ctx->rule_deatils, (char*)p->text);
*out_http_status = NGX_HTTP_FORBIDDEN;
return MATCHED;
}
}
}

return NOT_MATCHED;
Expand Down Expand Up @@ -488,17 +504,6 @@ static ngx_int_t ngx_http_waf_handler_check_black_cookie(ngx_http_request_t* r,
}


static ngx_int_t ngx_http_waf_handler_check_ipv4(unsigned long ip, const ipv4_t* ipv4) {
size_t prefix = ip & ipv4->suffix;

if (prefix == ipv4->prefix) {
return MATCHED;
}

return NOT_MATCHED;
}


static ngx_int_t ngx_http_waf_free_hash_table(ngx_http_request_t* r, ngx_http_waf_srv_conf_t* srv_conf) {
hash_table_item_int_ulong_t* p = NULL;
int count = 0;
Expand Down
73 changes: 73 additions & 0 deletions inc/ngx_http_waf_module_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,15 @@ static char* ngx_http_waf_rule_path_conf(ngx_conf_t* cf, ngx_command_t* cmd, voi
char* end = to_c_str((u_char*)full_path, srv_conf->waf_rule_path);

CHECK_AND_LOAD_CONF(cf, full_path, end, IPV4_FILE, srv_conf->black_ipv4, 1);
CHECK_AND_LOAD_CONF(cf, full_path, end, IPV6_FILE, srv_conf->black_ipv6, 2);
CHECK_AND_LOAD_CONF(cf, full_path, end, URL_FILE, srv_conf->black_url, 0);
CHECK_AND_LOAD_CONF(cf, full_path, end, ARGS_FILE, srv_conf->black_args, 0);
CHECK_AND_LOAD_CONF(cf, full_path, end, UA_FILE, srv_conf->black_ua, 0);
CHECK_AND_LOAD_CONF(cf, full_path, end, REFERER_FILE, srv_conf->black_referer, 0);
CHECK_AND_LOAD_CONF(cf, full_path, end, COOKIE_FILE, srv_conf->black_cookie, 0);
CHECK_AND_LOAD_CONF(cf, full_path, end, POST_FILE, srv_conf->black_post, 0);
CHECK_AND_LOAD_CONF(cf, full_path, end, WHITE_IPV4_FILE, srv_conf->white_ipv4, 1);
CHECK_AND_LOAD_CONF(cf, full_path, end, WHITE_IPV6_FILE, srv_conf->white_ipv6, 2);
CHECK_AND_LOAD_CONF(cf, full_path, end, WHITE_URL_FILE, srv_conf->white_url, 0);
CHECK_AND_LOAD_CONF(cf, full_path, end, WHITE_REFERER_FILE, srv_conf->white_referer, 0);

Expand Down Expand Up @@ -264,13 +266,15 @@ static void* ngx_http_waf_create_srv_conf(ngx_conf_t* cf) {
srv_conf->waf_cc_deny_limit = NGX_CONF_UNSET;
srv_conf->waf_cc_deny_duration = NGX_CONF_UNSET;
srv_conf->black_ipv4 = ngx_array_create(cf->pool, 10, sizeof(ipv4_t));
srv_conf->black_ipv6 = ngx_array_create(cf->pool, 10, sizeof(ipv6_t));
srv_conf->black_url = ngx_array_create(cf->pool, 10, sizeof(ngx_regex_elt_t));
srv_conf->black_args = ngx_array_create(cf->pool, 10, sizeof(ngx_regex_elt_t));
srv_conf->black_ua = ngx_array_create(cf->pool, 10, sizeof(ngx_regex_elt_t));
srv_conf->black_referer = ngx_array_create(cf->pool, 10, sizeof(ngx_regex_elt_t));
srv_conf->black_cookie = ngx_array_create(cf->pool, 10, sizeof(ngx_regex_elt_t));
srv_conf->black_post = ngx_array_create(cf->pool, 10, sizeof(ngx_regex_elt_t));
srv_conf->white_ipv4 = ngx_array_create(cf->pool, 10, sizeof(ipv4_t));
srv_conf->white_ipv6 = ngx_array_create(cf->pool, 10, sizeof(ipv6_t));
srv_conf->white_url = ngx_array_create(cf->pool, 10, sizeof(ngx_regex_elt_t));
srv_conf->white_referer = ngx_array_create(cf->pool, 10, sizeof(ngx_regex_elt_t));
srv_conf->ipv4_times = NULL;
Expand Down Expand Up @@ -408,4 +412,73 @@ static ngx_int_t ngx_http_waf_init_after_load_config(ngx_conf_t* cf) {
return NGX_OK;
}

static ngx_int_t load_into_array(ngx_conf_t* cf, const char* file_name, ngx_array_t* ngx_array, ngx_int_t mode) {
FILE* fp = fopen(file_name, "r");
ngx_int_t line_number = 0;
ngx_str_t line;
char* str = ngx_palloc(cf->pool, sizeof(char) * RULE_MAX_LEN);
if (fp == NULL) {
return FAIL;
}
while (fgets(str, RULE_MAX_LEN - 16, fp) != NULL) {
ngx_regex_compile_t regex_compile;
u_char errstr[NGX_MAX_CONF_ERRSTR];
ngx_regex_elt_t* ngx_regex_elt;
ipv4_t* ipv4;
ipv6_t* ipv6;
++line_number;
line.data = (u_char*)str;
line.len = strlen((char*)str);

if (line.len <= 0) {
continue;
}

if (line.data[line.len - 1] == '\n') {
line.data[line.len - 1] = '\0';
--(line.len);
if (line.len <= 0) {
continue;
}
if (line.data[line.len - 1] == '\r') {
line.data[line.len - 1] = '\0';
--(line.len);
}
}

if (line.len <= 0) {
continue;
}

switch (mode) {
case 0:
ngx_memzero(&regex_compile, sizeof(ngx_regex_compile_t));
regex_compile.pattern = line;
regex_compile.pool = cf->pool;
regex_compile.err.len = NGX_MAX_CONF_ERRSTR;
regex_compile.err.data = errstr;
ngx_regex_compile(&regex_compile);
ngx_regex_elt = ngx_array_push(ngx_array);
ngx_regex_elt->name = ngx_palloc(cf->pool, sizeof(u_char) * RULE_MAX_LEN);
to_c_str(ngx_regex_elt->name, line);
ngx_regex_elt->regex = regex_compile.regex;
break;
case 1:
ipv4 = ngx_array_push(ngx_array);
if (parse_ipv4(line, ipv4) != SUCCESS) {
return FAIL;
}
break;
case 2:
ipv6 = ngx_array_push(ngx_array);
if (parse_ipv6(line, ipv6) != SUCCESS) {
return FAIL;
}
}
}
fclose(fp);
ngx_pfree(cf->pool, str);
return SUCCESS;
}

#endif // !NGX_HTTP_WAF_MODULE_CONFIG_H
2 changes: 2 additions & 0 deletions inc/ngx_http_waf_module_macro.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

/* 对应配置文件的文件名 */
#define IPV4_FILE ("ipv4")
#define IPV6_FILE ("ipv6")
#define URL_FILE ("url")
#define ARGS_FILE ("args")
#define UA_FILE ("user-agent")
#define REFERER_FILE ("referer")
#define COOKIE_FILE ("cookie")
#define POST_FILE ("post")
#define WHITE_IPV4_FILE ("white-ipv4")
#define WHITE_IPV6_FILE ("white-ipv6")
#define WHITE_URL_FILE ("white-url")
#define WHITE_REFERER_FILE ("white-referer")

Expand Down
26 changes: 21 additions & 5 deletions inc/ngx_http_waf_module_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/
typedef struct {
int key; /**< IPV4 的整数形式 */
unsigned long times; /**< 该地址的请求次数 */
uint32_t times; /**< 该地址的请求次数 */
time_t start_time; /**< 首次记录本 IP 的时间 */
UT_hash_handle hh; /**< uthash 关键成员 */
} hash_table_item_int_ulong_t;
Expand Down Expand Up @@ -52,15 +52,17 @@ typedef struct {
ngx_int_t waf_mult_mount; /**< 是否执行多阶段检查 */
ngx_uint_t waf_mode; /**< 检测模式 */
ngx_int_t waf_cc_deny_limit; /**< CC 防御的限制频率 */
ngx_int_t waf_cc_deny_duration; /**< CC 防御的拉黑时长 */
ngx_int_t waf_cc_deny_duration; /**< CC 防御的拉黑时长(分钟) */
ngx_array_t *black_ipv4; /**< IPV4 黑名单 */
ngx_array_t *black_ipv6; /**< IPV6 黑名单 */
ngx_array_t *black_url; /**< URL 黑名单 */
ngx_array_t *black_args; /**< args 黑名单 */
ngx_array_t *black_ua; /**< user-agent 黑名单 */
ngx_array_t *black_referer; /**< Referer 黑名单 */
ngx_array_t *black_cookie; /**< Cookie 黑名单 */
ngx_array_t *black_post; /**< 请求体内容黑名单 */
ngx_array_t *white_ipv4; /**< IPV4 白名单 */
ngx_array_t *white_ipv6; /**< IPV6 白名单 */
ngx_array_t *white_url; /**< URL 白名单 */
ngx_array_t *white_referer; /**< Referer 白名单 */
hash_table_item_int_ulong_t *ipv4_times; /**< IPV4 访问频率统计表 */
Expand All @@ -75,11 +77,25 @@ typedef struct {
/**
* @struct ipv4_t
* @brief 格式化后的 IPV4
* @note 注意,无论是 prefix 还是 suffix 都是网络字节序,即大端字节序。
*/
typedef struct {
u_char text[32]; /**< 点分十进制表示法 */
size_t prefix; /**< 相当于 192.168.1.0/24 中的 192.168.1.0 的整数形式 */
size_t suffix; /**< 相当于 192.168.1.0/24 中的 24 的整数形式 */
u_char text[32]; /**< 点分十进制表示法 */
uint32_t prefix; /**< 相当于 192.168.1.0/24 中的 192.168.1.0 的整数形式 */
uint32_t suffix; /**< 相当于 192.168.1.0/24 中的 24 */
}ipv4_t;


/**
* @struct ipv6_t
* @brief 格式化后的 IPV6
* @note 注意,无论是 prefix[16] 还是 suffix[16],他们中的每一项都是网络字节序。
* 数组的下标同理,下标零代表最高位,下标十五代表最低位。
*/
typedef struct {
u_char text[64]; /**< 冒号十六进制表示法 */
uint8_t prefix[16]; /**< 相当于 ffff::ffff/64 中的 ffff::ffff 的整数形式 */
uint8_t suffix[16]; /**< 相当于 ffff::ffff/64 中的 64 */
}ipv6_t;

#endif // !NGX_HTTP_WAF_MODULE_TYPE_H
Loading

0 comments on commit 73a22eb

Please sign in to comment.