IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> nginx dns域名解析源码分析 -> 正文阅读

[PHP知识库]nginx dns域名解析源码分析

本文主要内容

域名查询的函数接口介绍

域名解析流程分析

查询场景分析及实现介绍

域名查询的函数

查询过程

为了不阻塞当前线程,nginx采用了异步的方式进行域名查询。

整个查询过程主要分为三个步骤:

1、准备函数调用需要的信息,并设置回调方法

2、调用函数

3、处理结束后回调方法被调用

使用同步io的情况时,调用gethostbyname()或gethostbyname_r(),根据域名查询IP。

由此可能会进行公网递归查询耗时较长。

为了尽量减少查询花费的时间,nginx还对查询结果做了本地缓存。

为了初始化DNS Server地址和本地缓存等信息,需要在真正查询前需要先进行一些全局的初始化操作。

详细分析

初始化域名查询所需要的全局信息

需要初始化的全局信息包括:

DNS服务器的地址如指定了多个,nginx会采用Round Robin轮流查询每个服务器。

查询结果缓存采用rb?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 服务器的信息需要在配置

resolver?8.8.8.8

#nginx 默认会根据DNS请求结果里的TTL值来进行缓存,

#当然也可以通过一个可选的参数valid来设置过期时间,如:

#resolver 127.0.0.1 [::1]:5353 valid=30s;

根据配置中的resolver参数,初始化全局的ngx_resolver_t其保存了前面提及的DNS服务器地址和查询结果等信息:

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进行域名查询的流程图如下,调用过程分为三种:

首先判断是不是IPv4地址,如果是就直接调用handler

再次检查是不是在缓存中,如果有,就调用handler

最后发送远程DNS请求,收到回复后调用handler

查询场景分析及实现介绍

查询的地址是IP v4地址如166.177.188.100,nginx会在ngx_resolve_start中通过ngx_inet_addr方法进行判断,如果是IPv4的地址,就设置好标志位?ngx_resolver_ctx_t->quick,在接下来的ngx_resolve_name中会对这个标志位进行判断,如果为1,就直接调用ngx_resolver_ctx_t->handler

ngx_resolver_ctx_t *

ngx_resolve_start(ngx_resolver_t *r, ngx_resolver_ctx_t *temp)

{

????in_addr_t??????????? addr;

????ngx_resolver_ctx_t? *ctx;



????if (temp) {

????????addr = ngx_inet_addr(temp->name.data, temp->name.len);



????????if (addr != INADDR_NONE) {

????????????temp->resolver = r;

????????????temp->state = NGX_OK;

????????????temp->naddrs = 1;

????????????temp->addrs = &temp->addr;

????????????temp->addr = addr;

????????????temp->quick = 1;



????????????return temp;

????????}

????}

????...

}

超时没有得到查询结果

调用ngx_resolve_name时设置的回调方法被调用,同时ngx_resolver_ctx_t->state被设置为NGX_RESOLVE_TIMEDOUT。相应的代码为:

static void

ngx_resolver_timeout_handler(ngx_event_t *ev)

{

????ngx_resolver_ctx_t? *ctx;

????ctx = ev->data;

????ctx->state = NGX_RESOLVE_TIMEDOUT;

????ctx->handler(ctx);

}

正常查询一个不在缓存中的域名

如果要查询的域名不在缓存中,首先把域名按hash值放在缓存中,然后准备查询需要的数据,发送DNS查询的UDP请求给DNS服务器,

static ngx_int_t

ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx)

{

????ngx_resolver_node_t? *rn;

????rn = ngx_resolver_alloc(r, sizeof(ngx_resolver_node_t));

????ngx_rbtree_insert(&r->name_rbtree, &rn->node);

????ngx_resolver_create_name_query(rn, ctx);

????ngx_resolver_send_query(r, rn);



????rn->cnlen = 0;

????rn->naddrs = 0;

????rn->valid = 0;

????rn->waiting = ctx;



????ctx->state = NGX_AGAIN;

}

收到DNS查询结果后的回调方法

static void

ngx_resolver_read_response(ngx_event_t *rev)

{

????ssize_t??????????? n;

????ngx_connection_t? *c;

????u_char???????????? buf[NGX_RESOLVER_UDP_SIZE];

????c = rev->data;



????do {

????????n = ngx_udp_recv(c, buf, NGX_RESOLVER_UDP_SIZE);

????????if (n < 0) {

????????????return;

????????}



????????ngx_resolver_process_response(c->data, buf, n);

????} while (rev->ready);

}

static void

ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t last,

????ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan, ngx_uint_t ans)

{

????hash = ngx_crc32_short(name.data, name.len);

????rn = ngx_resolver_lookup_name(r, &name, hash);



????//copy addresses to cached node

????rn->u.addrs = addrs;



????//回调所有等待本域名解析的请求

????next = rn->waiting;

????rn->waiting = NULL;



????while (next) {

?????????ctx = next;

?????????ctx->state = NGX_OK;

?????????ctx->naddrs = naddrs;

?????????ctx->addrs = (naddrs == 1) ? &ctx->addr : addrs;

?????????ctx->addr = addr;

?????????next = ctx->next;



?????????ctx->handler(ctx);

????}

}

对同一域名查询多次查询

如果多次查询时,之前的查询结果还在缓存中并且没有失效,就直接从缓存中取到查询结果,并调用设置的回调方法。

static ngx_int_t

ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx)

{

????uint32_t????????????? hash;

????in_addr_t???????????? addr, *addrs;

????ngx_uint_t??????????? naddrs;

????ngx_resolver_ctx_t?? *next;

????ngx_resolver_node_t? *rn;



????hash = ngx_crc32_short(ctx->name.data, ctx->name.len);

????rn = ngx_resolver_lookup_name(r, &ctx->name, hash);



????if (rn) {

????????if (rn->valid >= ngx_time()) {

????????????naddrs = rn->naddrs;



????????????if (naddrs) {

????????????????ctx->next = rn->waiting;

????????????????rn->waiting = NULL;



????????????????do {

????????????????????ctx->state = NGX_OK;

????????????????????ctx->naddrs = naddrs;

????????????????????ctx->addrs = (naddrs == 1) ? &ctx->addr : addrs;

????????????????????ctx->addr = addr;

????????????????????next = ctx->next;



????????????????????ctx->handler(ctx);



????????????????????ctx = next;

????????????????} while (ctx);



????????????????return NGX_OK;

????????????}

????????}

????}

}

得到查询结果时同时超时了

如果在得到查询结果的同时,设置的超时时间也到期了,那该怎么办呢? Nginx会先处理各种网络读写事件,再处理超时事件,在处理网络事件时,会相应地把设置的定时器删除,所以在执行超时事件时就不会再执行了。

void

ngx_process_events_and_timers(ngx_cycle_t *cycle)

{

????ngx_uint_t? flags;

????ngx_msec_t? timer, delta;



????//处理各种网络事件

????(void) ngx_process_events(cycle, timer, flags);



????//处理各种timer事件,其中包含了查询超时

????ngx_event_expire_timers();

}

得到查询结果时客户端已经关闭连接

如果不做任何处理,那么在收到dns查询结果后,会回调查询时设置的回调方法,但因为连接已经被关闭,相应的内存已经被释放,所以会有非法内存访问的问题。怎么避免呢?在处理连接关闭事件时,同时需要调用ngx_resolve_name_done(ctx)方法,调用时需要把state设为NGX_AGAIN或者NGX_RESOLVE_TIMEDOUT,这样就会删除查询所设置的回调信息:

void ngx_close_xxx_session(ngx_xxx_session_t *s)

{

????if(s->resolver_ctx != NULL) {

????????s->resolver_ctx->state = NGX_RESOLVE_TIMEDOUT;

????????ngx_resolve_name_done(s->resolver_ctx);

????????s->resolver_ctx = NULL;

????}

}

void ngx_resolve_name_done(ngx_resolver_ctx_t *ctx)

{

????uint32_t????????????? hash;

????ngx_resolver_t?????? *r;

????ngx_resolver_ctx_t?? *w, **p;

????ngx_resolver_node_t? *rn;



????r = ctx->resolver;

????if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) {

????????hash = ngx_crc32_short(ctx->name.data, ctx->name.len);

????????rn = ngx_resolver_lookup_name(r, &ctx->name, hash);



????????if (rn) {

????????????p = &rn->waiting;

????????????w = rn->waiting;



????????????while (w) {

????????????????if (w == ctx) {

????????????????????*p = w->next;

????????????????????goto done;

????????????????}



????????????????p = &w->next;

????????????????w = w->next;

????????????}

????????}

????}



done:

????ngx_resolver_free_locked(r, ctx);

}

本地缓存的地址没有再次被查询

每次在查询结束的时候(调用ngx_resolve_addr_done),都会检查有没有缓存过期,如果有,就会进行释放。

static void

ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree,

????????????????????ngx_queue_t *queue)

{

????time_t??????????????? now;

????ngx_uint_t??????????? i;

????ngx_queue_t????????? *q;

????ngx_resolver_node_t? *rn;

????now = ngx_time();



????for (i = 0; i < 2; i++) {

????????if (ngx_queue_empty(queue)) {

????????????return;

????????}



????????q = ngx_queue_last(queue);

????????rn = ngx_queue_data(q, ngx_resolver_node_t, queue);



????????if (now <= rn->expire) {

????????????return;

????????}



????????ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0,

????????????"resolver expire "%*s"", (size_t) rn->nlen, rn->name);



????????ngx_queue_remove(q);

????????ngx_rbtree_delete(tree, &rn->node);

????????ngx_resolver_free_node(r, rn);

????}

}

域名对应这多个IP地址

如果对应的有多个ip,那么在每次查询时,会随机的重新排列顺序,然后返回。

对于调用者来说,只要去第一个地址,就可以达到取随机地址的目的了。

static ngx_int_t

ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx)

{

????if (naddrs) {

????????if (naddrs != 1) {

????????????addr = 0;

????????????addrs = ngx_resolver_rotate(r, rn->u.addrs, naddrs);

????????????if (addrs == NULL) {

????????????????return NGX_ERROR;

????????????}



????????} else {

????????????addr = rn->u.addr;

????????????addrs = NULL;

????????}

????}

}

static in_addr_t *

ngx_resolver_rotate(ngx_resolver_t *r, in_addr_t *src, ngx_uint_t n)

{

????void??????? *dst, *p;

????ngx_uint_t?? j;



????dst = ngx_resolver_alloc(r, n * sizeof(in_addr_t));

????j = ngx_random() % n;



????if (j == 0) {

????????ngx_memcpy(dst, src, n * sizeof(in_addr_t));

????????return dst;

????}



????p = ngx_cpymem(dst, &src[j], (n - j) * sizeof(in_addr_t));

????ngx_memcpy(p, src, j * sizeof(in_addr_t));



????return dst;

}

指定了多个dns server地址会怎么查询

如果在配置文件里指定了多个dns server如:

resolver?8.8.8.8 8.8.4.4

会采用Round Robin 的方式轮流查询各个dns server。

在方法ngx_resolver_send_query中通过在每次调用时改变last_connection来轮流使用不同的dns server进行查询

static ngx_int_t

ngx_resolver_send_query(ngx_resolver_t *r, ngx_resolver_node_t *rn)

{

????ssize_t??????????????? n;

????ngx_udp_connection_t? *uc;



????uc = r->udp_connections.elts;



????uc = &uc[r->last_connection++];

????if (r->last_connection == r->udp_connections.nelts) {

????????r->last_connection = 0;

????}

????...

}

结束

2021 09 10 19:28?晴?19°~28°C? 南风<3级

Good night~

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-09-11 18:34:35  更:2021-09-11 18:34:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 18:47:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计