nginx--ngx_http_access_module 分析
模块整体结构
先附上它的ngx_module_t
结构体
ngx_module_t ngx_http_access_module = {
NGX_MODULE_V1,
&ngx_http_access_module_ctx, /* module context */
ngx_http_access_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_module_t ngx_http_access_module_ctx = {
NULL, /* preconfiguration */
ngx_http_access_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_access_create_loc_conf, /* create location configuration */
ngx_http_access_merge_loc_conf /* merge location configuration */
};
复制代码
ngx_http_access_module
实现了postconfiguration
、create_loc_conf
、merge_loc_conf
三个回调函数。
http{}块,server{}块,location{}块在进行解析时都会调用到各个http模块的create_loc_conf
方法,生成一个指向指向子模块配置结构体的指针,并把这个指针保存在各自块的ctx->loc_conf[ctx_index]
中。
static void *
ngx_http_access_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_access_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_access_loc_conf_t));
if (conf == NULL) {
return NULL;
}
return conf;
}
复制代码
如上代码所示,ngx_http_access_create_loc_conf
生成了一个ngx_http_access_loc_conf_t
结构体返回了出来。
typedef struct {
ngx_array_t *rules; /* array of ngx_http_access_rule_t */
#if (NGX_HAVE_INET6)
ngx_array_t *rules6; /* array of ngx_http_access_rule6_t */
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
ngx_array_t *rules_un; /* array of ngx_http_access_rule_un_t */
#endif
} ngx_http_access_loc_conf_t;
复制代码
ngx_http_access_loc_conf_t
里存储的就是ngx_http_access_module的配置项。
在所有的http{}内的配置项都被解析完后(包括http{}块内的嵌套块server{}、location{}),ngx_http_block
函数会调用merge_loc_conf
函数合并配置项,合并规则如下:
-
若 HTTP 模块实现了
merge_loc_conf
方法,则将 http{} 块下create_loc_conf
生成的结构体与嵌套每一个server{} 配置块下的结构体进行merge_loc_conf
操作; -
若 HTTP 模块实现了
merge_loc_conf
方法,则将server{} 块下create_loc_conf
生成的结构体与嵌套每一个location{}配置块下的结构体进行merge_loc_conf
操作; -
若 HTTP 模块实现了
merge_loc_conf
方法,则将location{} 块下create_loc_conf
生成的结构体与嵌套每一个location{}配置块下的结构体进行merge_loc_conf
操作;
static char *
ngx_http_access_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_access_loc_conf_t *prev = parent;
ngx_http_access_loc_conf_t *conf = child;
if (conf->rules == NULL
#if (NGX_HAVE_INET6)
&& conf->rules6 == NULL
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
&& conf->rules_un == NULL
#endif
) {
conf->rules = prev->rules;
#if (NGX_HAVE_INET6)
conf->rules6 = prev->rules6;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
conf->rules_un = prev->rules_un;
#endif
}
return NGX_CONF_OK;
}
复制代码
ngx_http_access_merge_loc_conf
采取的是不合并的策略。
模块指令解析
看一下它定义了那些指令:
static ngx_command_t ngx_http_access_commands[] = {
{ ngx_string("allow"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
|NGX_CONF_TAKE1,
ngx_http_access_rule,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
{ ngx_string("deny"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
|NGX_CONF_TAKE1,
ngx_http_access_rule,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
复制代码
附上http配置项位置说明
http {
/* NGX_HTTP_MAIN_CONF类型的指令在这里 */
upstream test {
/* NGX_HTTP_UPS_CONF 类型的指令在这里 */
...
}
server {
/* NGX_HTTP_SRV_CONF 指令在这里 */
if (...) {
/* NGX_HTTP_SIF_CONF 在这里 */
}
location / {
/* NGX_HTTP_LOC_CONF类型的指令在这里 */
limit_except GET {
/* NGX_HTTP_LMT_CONF 类型的指令在这里 */
...
}
if (...) {
/* NGX_HTTP_LIF_CONF类型的指令在这里 */
}
}
}
}
复制代码
allow
指令
该指令允许出现的位置:http{}内,server{}内,location{}内,limit_except{}内
该指令可携带的参数数量:only one
该指令存储位置:ngx_http_conf_ctx_t.loc_conf
deny
指令 该指令允许出现的位置:http{}内,server{}内,location{}内,limit_except{}内
该指令可携带的参数数量:only one
该指令存储位置:ngx_http_conf_ctx_t.loc_conf
继续分析配置解析时的回调函数:ngx_http_access_rule
static char *
ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_access_loc_conf_t *alcf = conf;
ngx_int_t rc;
ngx_uint_t all;
ngx_str_t *value;
ngx_cidr_t cidr;
ngx_http_access_rule_t *rule;
#if (NGX_HAVE_INET6)
ngx_http_access_rule6_t *rule6;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
ngx_http_access_rule_un_t *rule_un;
#endif
all = 0;
ngx_memzero(&cidr, sizeof(ngx_cidr_t));
value = cf->args->elts;
if (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0) {
all = 1;
#if (NGX_HAVE_UNIX_DOMAIN)
} else if (value[1].len == 5 && ngx_strcmp(value[1].data, "unix:") == 0) {
cidr.family = AF_UNIX;
#endif
} else {
rc = ngx_ptocidr(&value[1], &cidr);
if (rc == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
if (rc == NGX_DONE) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"low address bits of %V are meaningless", &value[1]);
}
}
if (cidr.family == AF_INET || all) {
if (alcf->rules == NULL) {
alcf->rules = ngx_array_create(cf->pool, 4,
sizeof(ngx_http_access_rule_t));
if (alcf->rules == NULL) {
return NGX_CONF_ERROR;
}
}
rule = ngx_array_push(alcf->rules);
if (rule == NULL) {
return NGX_CONF_ERROR;
}
rule->mask = cidr.u.in.mask;
rule->addr = cidr.u.in.addr;
rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
}
#if (NGX_HAVE_INET6)
if (cidr.family == AF_INET6 || all) {
if (alcf->rules6 == NULL) {
alcf->rules6 = ngx_array_create(cf->pool, 4,
sizeof(ngx_http_access_rule6_t));
if (alcf->rules6 == NULL) {
return NGX_CONF_ERROR;
}
}
rule6 = ngx_array_push(alcf->rules6);
if (rule6 == NULL) {
return NGX_CONF_ERROR;
}
rule6->mask = cidr.u.in6.mask;
rule6->addr = cidr.u.in6.addr;
rule6->deny = (value[0].data[0] == 'd') ? 1 : 0;
}
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
if (cidr.family == AF_UNIX || all) {
if (alcf->rules_un == NULL) {
alcf->rules_un = ngx_array_create(cf->pool, 1,
sizeof(ngx_http_access_rule_un_t));
if (alcf->rules_un == NULL) {
return NGX_CONF_ERROR;
}
}
rule_un = ngx_array_push(alcf->rules_un);
if (rule_un == NULL) {
return NGX_CONF_ERROR;
}
rule_un->deny = (value[0].data[0] == 'd') ? 1 : 0;
}
#endif
return NGX_CONF_OK;
}
复制代码
在ngx_http_access_rule
函数中主要做了以下几件事:
- 取出配置文件中读取到的配置项,把ip地址字符串转换成
ngx_cidr_t
的形式保存下来 - 查看配置项结构体里的规则数组是否已经被创建出来了,如果没有则创建,之后把配置规则投放到数组中\
模块注入点
新增模块想要依赖于nginx处理http请求,需要实现处理函数,然后设置函数的挂载点。
设置函数挂载点是在ngx_http_access_init
中进行设置的,ngx_http_access_init
函数本身的调用又是挂载在了配置解析流程中的postconfiguration
上。
postconfiguration
在ngx_http_block
基本处理完配置解析后被调用。
static ngx_int_t
ngx_http_access_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_access_handler;
return NGX_OK;
}
复制代码
ngx_http_access_init
主要做了以下几件事:
- 获取到
ngx_http_core_module
的main conf - 向http处理流程阶段
NGX_HTTP_ACCESS_PHASE
中投放了需要被调用的函数指针ngx_http_access_handler
在一个http请求执行到NGX_HTTP_ACCESS_PHASE
阶段时,就会循环调用cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers
中的函数指针,这样子就会调用到ngx_http_access_handler
static ngx_int_t
ngx_http_access_handler(ngx_http_request_t *r)
{
struct sockaddr_in *sin;
ngx_http_access_loc_conf_t *alcf;
#if (NGX_HAVE_INET6)
u_char *p;
in_addr_t addr;
struct sockaddr_in6 *sin6;
#endif
alcf = ngx_http_get_module_loc_conf(r, ngx_http_access_module);
switch (r->connection->sockaddr->sa_family) {
case AF_INET:
if (alcf->rules) {
sin = (struct sockaddr_in *) r->connection->sockaddr;
return ngx_http_access_inet(r, alcf, sin->sin_addr.s_addr);
}
break;
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
p = sin6->sin6_addr.s6_addr;
if (alcf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
addr = p[12] << 24;
addr += p[13] << 16;
addr += p[14] << 8;
addr += p[15];
return ngx_http_access_inet(r, alcf, htonl(addr));
}
if (alcf->rules6) {
return ngx_http_access_inet6(r, alcf, p);
}
break;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
case AF_UNIX:
if (alcf->rules_un) {
return ngx_http_access_unix(r, alcf);
}
break;
#endif
}
return NGX_DECLINED;
}
复制代码
该模块的主逻辑在ngx_http_access_inet
中:
思路就是循环遍历所有规则,如果ip能匹配上,则根据配置是否允许决定该数据包的后续处理是否继续。
static ngx_int_t
ngx_http_access_inet(ngx_http_request_t *r, ngx_http_access_loc_conf_t *alcf,
in_addr_t addr)
{
ngx_uint_t i;
ngx_http_access_rule_t *rule;
rule = alcf->rules->elts;
for (i = 0; i < alcf->rules->nelts; i++) {
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"access: %08XD %08XD %08XD",
addr, rule[i].mask, rule[i].addr);
if ((addr & rule[i].mask) == rule[i].addr) {
return ngx_http_access_found(r, rule[i].deny);
}
}
return NGX_DECLINED;
}
static ngx_int_t
ngx_http_access_found(ngx_http_request_t *r, ngx_uint_t deny)
{
ngx_http_core_loc_conf_t *clcf;
if (deny) {
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (clcf->satisfy == NGX_HTTP_SATISFY_ALL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"access forbidden by rule");
}
return NGX_HTTP_FORBIDDEN;
}
return NGX_OK;
}
复制代码