標籤:
Nginx怎麼做網域名稱解析?怎麼在你自己開發的模組裡面使用Nginx提供的方法解析網域名稱?它內部實現是什麼樣的?
本文以Nginx 1.5.1為例,從nginx_mail_smtp模組如何進行網域名稱解析出發,分析Nginx進行網域名稱解析的過程。為了簡化流程,突出重點,在範例程式碼中省掉了一些異常部分的處理,比如記憶體配置失敗等。DNS查詢分為兩種:根據網域名稱查詢地址和根據地址查詢網域名稱,在代碼結構上這兩種方式非常相似,這裡只介紹根據網域名稱查詢地址這一種方式。本文將從以下幾個方面進行介紹:
網域名稱查詢的函數介面介紹
網域名稱解析流程分析
查詢情境分析及實現介紹
一、網域名稱查詢的函數介面介紹
在使用同步IO的情況下,調用gethostbyname()或者gethostbyname_r()就可以根據網域名稱查詢到對應的IP地址, 但因為可能會通過網路進行遠程查詢,所以需要的時間比較長。
為了不阻塞當前線程,Nginx採用了非同步方式進行網域名稱查詢。整個查詢過程主要分為三個步驟,這點在各種非同步處理時都是一樣的:
準備函數調用需要的資訊,並設定回調方法
調用函數
處理結束後回調方法被調用
另外,為了盡量減少查詢花費的時間,Nginx還對查詢結果做了本機快取。為了初始化DNS Server地址和本機快取等資訊,需要在真正查詢前需要先進行一些全域的初始化操作。
下面先從調用者的角度對每個步驟做詳細的分析:
初始化網域名稱查詢所需要的的全域資訊
需要初始化的全域資訊包括:
DNS 伺服器的地址,如果指定了多個伺服器,nginx會採用Round Robin的方式輪流查詢每個伺服器
對查詢結果的緩衝,採用Red Black Tree的資料結構,以要查詢名字的Hash作為Key, 節點資訊存放在 struct ngx_resolver_node_t中。
因為resolver是全域的,與任何一個connection都無關,所有需要放在一個隨時都可以取到的地方,如 ngx_mail_core_srv_conf_t結構體上,在使用時從當前session找到ngx_mail_core_srv_conf_t,然後找到resolver。
DNS 伺服器的資訊需要在設定檔中明確指出,比如
1
2
3
4
5
6
#nginx.conf
resolver 8.8.8.8
#nginx 預設會根據DNS請求結果裡的TTL值來進行緩衝,
#當然也可以通過一個可選的參數valid來設定到期時間,如:
#resolver 127.0.0.1 [::1]:5353 valid=30s;
下面根據配置中的resolver參數,初始化全域的ngx_resolver_t,其中儲存了前面提及的DNS伺服器位址和查詢結果等資訊:
01
02
03
04
05
06
07
08
09
10
11
static char *
ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_mail_core_srv_conf_t *cscf = conf;
ngx_str_t *value;
value = cf->args->elts;
cscf->resolver = ngx_resolver_create(cf, &value[1],
cf->args->nelts - 1);
return NGX_CONF_OK;
}
準備本次查詢的資訊
和本次查詢相關的資訊放在ngx_resolver_ctx_t結構體中,包括要查詢的名稱,查詢完的回調方法,以及逾時時間等。如果本次要查詢的地址已經是IPv4用點分隔的地址了,比如74.125.128.100, nginx會在ngx_resolve_start中進行判斷,並設定好標誌位,在調用ngx_resolve_name時不會發送真正的DNS查詢請求
static void
ngx_mail_smtp_resolve_name(ngx_event_t *rev)
{
ngx_connection_t *c;
ngx_mail_session_t *s;
ngx_resolver_ctx_t *ctx;
ngx_mail_core_srv_conf_t *cscf;
c = rev->data;
s = c->data;
cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
ctx = ngx_resolve_start(cscf->resolver, NULL);
if (ctx == NULL) {
ngx_mail_close_connection(c);
return;
}
ctx->name = s->host;
ctx->type = NGX_RESOLVE_A;
ctx->handler = ngx_mail_smtp_resolve_name_handler;
ctx->data = s;
ctx->timeout = cscf->resolver_timeout;
//根據名字進行IP地址查詢
if (ngx_resolve_name(ctx) != NGX_OK) {
ngx_mail_close_connection(c);
}
}
根據名字進行IP地址查詢
前面方法的最後通過ngx_resolve_name方法進行IP地址查詢。查詢時,Nginx會先檢查本機快取,如果在緩衝中,就更新緩衝到期時間,並回調設定的handler, 如前面設定的:ngx_mail_smtp_resolve_name_handler,然後整個查詢過程結束。如果沒有在緩衝中就發送查詢請求給dns server,同時方法返回。
查詢完成後回調在ngx_resolver_ctx_t中指定的方法
真正的DNS查詢完成後,不管成功,失敗或是逾時,nginx會回調相應查詢的handler, 如前面設定的:ngx_mail_smtp_resolve_name_handler。在handler中都需要調用ngx_resolve_addr_done來標識查詢結束。
static void
ngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx)
{
in_addr_t addr;
ngx_uint_t i;
ngx_connection_t *c;
struct sockaddr_in *sin;
ngx_mail_session_t *s;
s = ctx->data;
c = s->connection;
if (ctx->state) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
""%V" could not be resolved (%i: %s)",
&ctx->name, ctx->state,
ngx_resolver_strerror(ctx->state));
} else {
/* AF_INET only */
sin = (struct sockaddr_in *) c->sockaddr;
for (i = 0; i < ctx->naddrs; i++) {
addr = ctx->addrs[i];
ngx_log_debug4(NGX_LOG_DEBUG_MAIL, c->log, 0,
"name was resolved to %ud.%ud.%ud.%ud",
(ntohl(addr) >> 24) & 0xff,
(ntohl(addr) >> 16) & 0xff,
(ntohl(addr) >> 8) & 0xff,
ntohl(addr) & 0xff);
if (addr == sin->sin_addr.s_addr) {
goto found;
}
}
s->host = smtp_unavailable;
}
found:
//不管成功失敗都要執行
ngx_resolve_name_done(ctx);
}
Nginx的DNS解析過程分析