small-package/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.c

701 lines
21 KiB
C
Raw Normal View History

2021-09-24 23:37:27 +08:00
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include "ngx_event_timer_module.h"
#include "ngx_event_resolver.h"
#include "ngx_dynamic_resolver.h"
#include "ngx_toolkit_misc.h"
static ngx_int_t ngx_dynamic_resolver_process_init(ngx_cycle_t *cycle);
static void *ngx_dynamic_resolver_create_conf(ngx_cycle_t *cycle);
static char *ngx_dynamic_resolver_init_conf(ngx_cycle_t *cycle, void *conf);
#define MAX_DOMAIN_LEN 128
#define MAX_ADDRS 8
typedef struct ngx_dynamic_resolver_ctx_s ngx_dynamic_resolver_ctx_t;
typedef struct ngx_dynamic_resolver_domain_s ngx_dynamic_resolver_domain_t;
struct ngx_dynamic_resolver_ctx_s {
ngx_dynamic_resolver_handler_pt h;
void *data;
ngx_dynamic_resolver_ctx_t *next;
};
typedef struct {
struct sockaddr sockaddr;
socklen_t socklen;
u_short priority;
u_short weight;
} ngx_dynamic_resolver_addr_t;
struct ngx_dynamic_resolver_domain_s {
ngx_str_t domain;
u_char domain_cstr[MAX_DOMAIN_LEN];
ngx_uint_t naddrs;
ngx_dynamic_resolver_addr_t addrs[MAX_ADDRS];
ngx_dynamic_resolver_ctx_t *ctx;
ngx_dynamic_resolver_domain_t *next;
};
typedef struct {
ngx_msec_t refresh_interval;
size_t domain_buckets;
ngx_dynamic_resolver_domain_t **resolver_hash;
ngx_dynamic_resolver_ctx_t *free_ctx;
ngx_dynamic_resolver_domain_t *free_domain;
ngx_uint_t nalloc_ctx;
ngx_uint_t nfree_ctx;
ngx_uint_t nalloc_domain;
ngx_uint_t nfree_domain;
} ngx_dynamic_resolver_conf_t;
static ngx_str_t dynamic_resolver_name = ngx_string("dynamic_resolver");
static ngx_command_t ngx_dynamic_resolver_commands[] = {
{ ngx_string("dynamic_refresh_interval"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
0,
offsetof(ngx_dynamic_resolver_conf_t, refresh_interval),
NULL },
{ ngx_string("dynamic_domain_buckets"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_dynamic_resolver_conf_t, domain_buckets),
NULL },
ngx_null_command
};
ngx_event_module_t ngx_dynamic_resolver_module_ctx = {
&dynamic_resolver_name,
ngx_dynamic_resolver_create_conf, /* create configuration */
ngx_dynamic_resolver_init_conf, /* init configuration */
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};
/* this module use ngx_cycle->log */
ngx_module_t ngx_dynamic_resolver_module = {
NGX_MODULE_V1,
&ngx_dynamic_resolver_module_ctx, /* module context */
ngx_dynamic_resolver_commands, /* module directives */
NGX_EVENT_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
ngx_dynamic_resolver_process_init, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static void *
ngx_dynamic_resolver_create_conf(ngx_cycle_t *cycle)
{
ngx_dynamic_resolver_conf_t *conf;
conf = ngx_pcalloc(cycle->pool, sizeof(ngx_dynamic_resolver_conf_t));
if (conf == NULL) {
return NULL;
}
conf->refresh_interval = NGX_CONF_UNSET_MSEC;
conf->domain_buckets = NGX_CONF_UNSET_UINT;
return conf;
}
static char *
ngx_dynamic_resolver_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_dynamic_resolver_conf_t *drcf = conf;
ngx_conf_init_msec_value(drcf->refresh_interval, 5000);
ngx_conf_init_uint_value(drcf->domain_buckets, 101);
if (drcf->refresh_interval > 0 && drcf->domain_buckets > 0) {
drcf->resolver_hash = ngx_pcalloc(cycle->pool,
sizeof(ngx_dynamic_resolver_domain_t *) * drcf->domain_buckets);
}
return NGX_CONF_OK;
}
/* reuse for ngx_dynamic_resolver_ctx_t */
static ngx_dynamic_resolver_ctx_t *
ngx_dynamic_resolver_get_ctx(ngx_cycle_t *cycle)
{
ngx_dynamic_resolver_conf_t *drcf;
ngx_dynamic_resolver_ctx_t *ctx;
drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module);
ctx = drcf->free_ctx;
if (ctx == NULL) {
ctx = ngx_pcalloc(cycle->pool, sizeof(ngx_dynamic_resolver_ctx_t));
if (ctx == NULL) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "dynamic resolver, "
"alloc memory dynamic resolver ctx failed");
return NULL;
}
++drcf->nalloc_ctx;
} else {
drcf->free_ctx = drcf->free_ctx->next;
ngx_memzero(ctx, sizeof(ngx_dynamic_resolver_ctx_t));
--drcf->nfree_ctx;
}
return ctx;
}
static void
ngx_dynamic_resolver_put_ctx(ngx_dynamic_resolver_ctx_t *ctx,
ngx_cycle_t *cycle)
{
ngx_dynamic_resolver_conf_t *drcf;
drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module);
ctx->next = drcf->free_ctx;
drcf->free_ctx = ctx;
++drcf->nfree_ctx;
}
/* reuse for ngx_dynamic_resolver_domain_t */
static ngx_dynamic_resolver_domain_t *
ngx_dynamic_resolver_get_domain(ngx_cycle_t *cycle)
{
ngx_dynamic_resolver_conf_t *drcf;
ngx_dynamic_resolver_domain_t *domain;
drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module);
domain = drcf->free_domain;
if (domain == NULL) {
domain = ngx_pcalloc(cycle->pool,
sizeof(ngx_dynamic_resolver_domain_t));
if (domain == NULL) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "dynamic resolver, "
"alloc memory dynamic resolver domain failed");
return NULL;
}
++drcf->nalloc_domain;
} else {
drcf->free_domain = drcf->free_domain->next;
ngx_memzero(domain, sizeof(ngx_dynamic_resolver_domain_t));
--drcf->nfree_domain;
}
return domain;
}
static void
ngx_dynamic_resolver_put_domain(ngx_dynamic_resolver_domain_t *domain,
ngx_cycle_t *cycle)
{
ngx_dynamic_resolver_conf_t *drcf;
drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module);
domain->next = drcf->free_domain;
drcf->free_domain = domain;
++drcf->nfree_domain;
}
static void
ngx_dynamic_resolver_on_result(void *data, ngx_resolver_addr_t *addrs,
ngx_uint_t naddrs)
{
ngx_dynamic_resolver_domain_t *domain;
ngx_dynamic_resolver_ctx_t *ctx;
ngx_uint_t i, n;
domain = data;
if (domain == NULL) {
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver, "
"%V has been deleted", &domain->domain);
return;
}
if (naddrs == 0) {
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver, "
"domain '%V' resolver failed", &domain->domain);
while (domain->ctx) {
ctx = domain->ctx;
domain->ctx = ctx->next;
ctx->h(ctx->data, NULL, 0);
ngx_dynamic_resolver_put_ctx(ctx, (ngx_cycle_t *) ngx_cycle);
}
return;
}
domain->naddrs = ngx_min(naddrs, MAX_ADDRS);
for (i = 0; i < domain->naddrs; ++i) {
ngx_memcpy(&domain->addrs[i].sockaddr, addrs[i].sockaddr,
addrs[i].socklen);
domain->addrs[i].socklen = addrs[i].socklen;
domain->addrs[i].priority = addrs[i].priority;
domain->addrs[i].weight = addrs[i].weight;
n = ngx_random() % domain->naddrs;
while (domain->ctx) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"dynamic resolver, resolver '%V' successd",
&domain->domain);
ctx = domain->ctx;
domain->ctx = ctx->next;
ctx->h(ctx->data, &domain->addrs[n].sockaddr,
domain->addrs[n].socklen);
ngx_dynamic_resolver_put_ctx(ctx, (ngx_cycle_t *) ngx_cycle);
++n;
n %= domain->naddrs;
}
}
}
static void
ngx_dynamic_resolver_on_timer(void *data)
{
ngx_dynamic_resolver_conf_t *drcf;
ngx_dynamic_resolver_domain_t *domain;
ngx_uint_t i;
if (ngx_exiting) {
return;
}
drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module);
for (i = 0; i < drcf->domain_buckets; ++i) {
domain = drcf->resolver_hash[i];
while (domain) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"dynamic resolver, on timer start resolver %V",
&domain->domain);
ngx_event_resolver_start_resolver(&domain->domain,
ngx_dynamic_resolver_on_result, domain);
domain = domain->next;
}
}
ngx_event_timer_add_timer(drcf->refresh_interval,
ngx_dynamic_resolver_on_timer, NULL);
}
static ngx_int_t
ngx_dynamic_resolver_process_init(ngx_cycle_t *cycle)
{
ngx_dynamic_resolver_conf_t *drcf;
drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module);
if (drcf->refresh_interval == 0) {
return NGX_OK;
}
ngx_event_timer_add_timer(0, ngx_dynamic_resolver_on_timer, NULL);
return NGX_OK;
}
void
ngx_dynamic_resolver_add_domain(ngx_str_t *domain, ngx_cycle_t *cycle)
{
ngx_dynamic_resolver_conf_t *drcf;
ngx_dynamic_resolver_domain_t *d;
ngx_uint_t idx;
struct sockaddr sa;
socklen_t len;
u_char temp[MAX_DOMAIN_LEN];
if (domain == NULL || domain->len == 0) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
"dynamic resolver add, domain is NULL");
return;
}
len = ngx_sock_pton(&sa, domain->data, domain->len);
/* addr is IP address */
if (len) {
ngx_log_error(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"dynamic resolver add, domain is ipv4/ipv6/unix address");
return;
}
drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module);
if (drcf->refresh_interval == 0) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "dynamic resolver add, "
"dynamic resolver closed when add domain");
return;
}
if (domain->len > MAX_DOMAIN_LEN) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "dynamic resolver add, "
"domain length(%z) is too long", domain->len);
return;
}
ngx_memzero(temp, MAX_DOMAIN_LEN);
idx = ngx_hash_strlow(temp, domain->data, domain->len);
idx %= drcf->domain_buckets;
ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0, "dynamic resolver add, "
"prepare add %V in %d slot", domain, idx);
for (d = drcf->resolver_hash[idx]; d; d = d->next) {
if (d->domain.len == domain->len &&
ngx_memcmp(d->domain.data, temp, domain->len) == 0)
{
ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"dynamic resolver add, %V is in dynamic resolv hash table",
domain);
return;
}
}
d = ngx_dynamic_resolver_get_domain(cycle);
if (d == NULL) {
return;
}
/* add domain in dynamic resolver */
d->next = drcf->resolver_hash[idx];
drcf->resolver_hash[idx] = d;
ngx_memcpy(d->domain_cstr, temp, MAX_DOMAIN_LEN);
d->domain.data = d->domain_cstr;
d->domain.len = domain->len;
}
void
ngx_dynamic_resolver_del_domain(ngx_str_t *domain)
{
ngx_dynamic_resolver_conf_t *drcf;
ngx_dynamic_resolver_domain_t **pd, *d;
ngx_dynamic_resolver_ctx_t *ctx;
ngx_uint_t idx;
struct sockaddr sa;
socklen_t len;
u_char temp[MAX_DOMAIN_LEN];
if (domain == NULL || domain->len == 0) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"dynamic resolver del, domain is NULL");
return;
}
len = ngx_sock_pton(&sa, domain->data, domain->len);
/* addr is IP address */
if (len) {
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"dynamic resolver del, domain is ipv4/ipv6/unix address");
return;
}
drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module);
if (drcf->refresh_interval == 0) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver del, "
"dynamic resolver closed when del domain");
return;
}
if (domain->len > MAX_DOMAIN_LEN) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver del, "
"domain length(%z) is too long", domain->len);
return;
}
ngx_memzero(temp, MAX_DOMAIN_LEN);
idx = ngx_hash_strlow(temp, domain->data, domain->len);
idx %= drcf->domain_buckets;
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver del, "
"prepare del %V in %d slot", domain, idx);
for (pd = &drcf->resolver_hash[idx]; *pd; pd = &(*pd)->next) {
if ((*pd)->domain.len == domain->len &&
ngx_memcmp((*pd)->domain.data, temp, domain->len) == 0)
{
d= *pd;
*pd = (*pd)->next;
while (d->ctx) {
ctx = d->ctx;
d->ctx = ctx->next;
ctx->h(ctx->data, NULL, 0);
ngx_dynamic_resolver_put_ctx(ctx, (ngx_cycle_t *) ngx_cycle);
}
ngx_dynamic_resolver_put_domain(d, (ngx_cycle_t *) ngx_cycle);
return;
}
}
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver del, "
"%V is not in dynamic resolv hash table", domain);
}
void
ngx_dynamic_resolver_start_resolver(ngx_str_t *domain,
ngx_dynamic_resolver_handler_pt h, void *data)
{
ngx_dynamic_resolver_conf_t *drcf;
ngx_dynamic_resolver_domain_t *d;
ngx_dynamic_resolver_ctx_t *ctx;
ngx_uint_t idx, n;
struct sockaddr sa;
socklen_t len;
u_char temp[MAX_DOMAIN_LEN];
if (domain == NULL || domain->len == 0) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"dynamic resolver async, domain is NULL");
return;
}
len = ngx_sock_pton(&sa, domain->data, domain->len);
/* addr is ipv4/ipv6/unix address */
if (len) {
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"dynamic resolver async, domain is ipv4/ipv6/unix address");
h(data, &sa, len);
return;
}
drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module);
if (drcf->refresh_interval == 0) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver async, "
"dynamic resolver closed when start resolver");
goto failed;
}
if (domain->len > MAX_DOMAIN_LEN) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver async, "
"domain length(%z) is too long", domain->len);
goto failed;
}
ngx_memzero(temp, MAX_DOMAIN_LEN);
idx = ngx_hash_strlow(temp, domain->data, domain->len);
idx %= drcf->domain_buckets;
d = drcf->resolver_hash[idx];
while (d) {
if (d->domain.len == domain->len &&
ngx_memcmp(d->domain.data, temp, domain->len) == 0)
{
break;
}
d = d->next;
}
if (d == NULL) { /* not found */
d = ngx_dynamic_resolver_get_domain((ngx_cycle_t *) ngx_cycle);
if (d == NULL) {
goto failed;
}
/* add domain in dynamic resolver */
d->next = drcf->resolver_hash[idx];
drcf->resolver_hash[idx] = d;
ngx_memcpy(d->domain_cstr, temp, MAX_DOMAIN_LEN);
d->domain.data = d->domain_cstr;
d->domain.len = domain->len;
}
/* domain is not resolved */
if (d->naddrs == 0) {
/* add call back in resolver list */
ctx = ngx_dynamic_resolver_get_ctx((ngx_cycle_t *) ngx_cycle);
if (ctx == NULL) {
goto failed;
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"dynamic resolver async, domain '%V' is not resolved",
&d->domain);
ctx->h = h;
ctx->data = data;
ctx->next = d->ctx;
d->ctx = ctx;
ngx_event_resolver_start_resolver(&d->domain,
ngx_dynamic_resolver_on_result, d);
return;
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"dynamic resolver async, domain '%V' is resolved, call directly",
&d->domain);
/* call callback */
n = ngx_random() % d->naddrs;
h(data, &d->addrs[n].sockaddr, d->addrs[n].socklen);
return;
failed:
h(data, NULL, 0);
}
socklen_t
ngx_dynamic_resolver_gethostbyname(ngx_str_t *domain, struct sockaddr *sa)
{
ngx_dynamic_resolver_conf_t *drcf;
ngx_dynamic_resolver_domain_t *d;
ngx_uint_t idx, n;
socklen_t len;
u_char temp[MAX_DOMAIN_LEN];
if (domain == NULL) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"dynamic resolver sync, domain is NULL");
return 0;
}
len = ngx_sock_pton(sa, domain->data, domain->len);
/* addr is ipv4/ipv6/unix address */
if (len) {
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"dynamic resolver sync, domain is ipv4/ipv6/unix address");
return len;
}
drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module);
if (drcf->refresh_interval == 0) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver sync, "
"dynamic resolver closed when start resolver");
return 0;
}
if (domain->len > MAX_DOMAIN_LEN) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver sync, "
"domain length(%z) is too long", domain->len);
return 0;
}
ngx_memzero(temp, MAX_DOMAIN_LEN);
idx = ngx_hash_strlow(temp, domain->data, domain->len);
idx %= drcf->domain_buckets;
d = drcf->resolver_hash[idx];
while (d) {
if (d->domain.len == domain->len &&
ngx_memcmp(d->domain.data, temp, domain->len) == 0)
{
if (d->naddrs == 0) {
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0,
"dynamic resolver sync, domain '%V' is not resolved",
&d->domain);
return 0;
}
n = ngx_random() % d->naddrs;
ngx_memcpy(sa, &d->addrs[n].sockaddr, d->addrs[n].socklen);
return d->addrs[n].socklen;
}
d = d->next;
}
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver sync, "
"domain '%V' is not in dynamic resolver table", domain);
return 0;
}
ngx_chain_t *
ngx_dynamic_resolver_state(ngx_http_request_t *r)
{
ngx_dynamic_resolver_conf_t *drcf;
ngx_chain_t *cl;
ngx_buf_t *b;
size_t len;
drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module);
len = sizeof("##########dynamic resolver state##########\n") - 1
+ sizeof("ngx_dynamic_resolver alloc ctx: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("ngx_dynamic_resolver free ctx: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("ngx_dynamic_resolver alloc domain: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("ngx_dynamic_resolver free domain: \n") - 1 + NGX_OFF_T_LEN;
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NULL;
}
cl->next = NULL;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NULL;
}
cl->buf = b;
b->last = ngx_snprintf(b->last, len,
"##########dynamic resolver state##########\n"
"ngx_dynamic_resolver alloc ctx: %ui\n"
"ngx_dynamic_resolver free ctx: %ui\n"
"ngx_dynamic_resolver alloc domain: %ui\n"
"ngx_dynamic_resolver free domain: %ui\n",
drcf->nalloc_ctx, drcf->nfree_ctx,
drcf->nalloc_domain, drcf->nfree_domain);
return cl;
}