前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Nginx域名解析流程,源码分析

Nginx域名解析流程,源码分析

作者头像
李俊鹏
发布2020-06-15 15:16:31
6.1K0
发布2020-06-15 15:16:31
举报
文章被收录于专栏:运维研习社运维研习社

nginx在做正向代理、反向代理的时候,或upstream使用域名的时候,要做频繁的域名解析,为了更快的响应,nginx有一套自己的域名解析过程

今天详细分析一下nginx的域名解析过程

在nginx中,只有两个配置指令关于域名解析,就是resolver,和resolver_timeout,resolver_timeout不多说,就是域名解析超时时间,这里具体就说resolver指令

简单配置了个nginx反向代理,如图,然后为了便于调试,只起了一个工作进程

先用strace看了下系统调用,在connect调用中已经解析到了baidu.com的IP地址

这和预想的不一样,原本以为是每次调用都会去查一次系统DNS,但是这里却看到没有查系统DNS,难道没有调用系统dns吗?自有一套?下面验证猜想

首先我把/etc/resolv.conf的nameserver改成个不可访问的,然后启动nginx

发现无法正常启动,报错解析不到域名的地址,那应该还是调用系统dns了,接着用strace看一下启动过程

前面部分就不截图了,基本就是调用各种系统组件,初始化的过程,到这里开始读取default.conf配置文件,然后开始解析proxy_pass后面的域名地址

可以看到过程如下:

首先查询nscd

接着查询/etc/host.conf

然后查询/etc/resolve.conf

接着通过nsswith进行解析,利用resolve文件中提供的nameserver地址

尝试发送解析多次后,解析失败

最后调用wirte输出错误

通过以上strace追踪发现,nginx是在启动的时候就调用系统dns进行域名解析操作,下面结合源码看下nginx启动的时候如何初始化域名解析

从上面分析,是在解析配置文件的时候才去做域名解析操作的,所以根据nginx初始化流程判断,直接查看nginx的http_core_module中可以看到对resolver的声明

然后在core/ngx_resolver.c中查看ngx_resolver_t的结构体

首先是typedef定义了别名

找到ngx_resolver_s查看结构体变量声明如下:

可以看到声明了dns查询,以及红黑树缓存dns数据,以及IPv6的处理

nginx在初始化的时候,通过core/ngx_resolver.c中的ngx_resolver_create来初始化上面的结构体,如果在配置文件中有设置resolver指令,则在启动的时候通过http/ngx_http_core_resolver进行调用

接着看下ngx_resolver_create做了什么

太长了,不贴代码了,这里解释下过程,有兴趣可以去看源码

这里主要就是配置解析阶段:

  • 设置cleanup的handler(ngx_resolver_cleanup)
  • 初始化保存域名节点信息的红黑树(r->name_rbtree)
  • 初始化重传和过期队列(r->name_resend_queue、r->name_expire_queue)
  • 设置超时时间的handler(ngx_resolver_resend_handler)
  • 解析dns server的ip并设置到地址数据(r->connections)
  • 解析参数(valid,ipv6)等

请求构造阶段:

通过ngx_resolver_start开始做解析,判断如果是IP地址,则temp->quick=1,直接返回IP地址

我们知道,通常只有在proxy_pass和upstream中进行域名配置,所以接着看下proxy_pass指令源码和upstream指令源码

代码比较长,在http/ngx_http_proxy_module.c中,在ngx_http_proxy_passs中,首先判断upstream中的域名配置和proxy_length,接着判断proxy_pass后面的url中最后是否是"/",如果是,自动跳转,接着判断url中变量数量,根据数量判断是http还是https协议,接着还是通过调用ngx_http_upstream_add,将域名添加到upstream解析队列中,所以所有的调用解析,还是从upstream中调用,接着看upstream

接着刚才ngx_http_upstream_add,proxy_pass中的url传入之后,开始通过ngx_http_upstream_create创建upstream,接着在upstream初始化中声明resolver并调用ngx_resolve_start解析域名

整个过程总结如下:

proxy_pass http://$host;

ngx_resolver_ctx_t ctx 每次域名解析都会生成这个结构体, 直接malloc,未使用r->pool.ctx = ngx_resolve_start()

• 如果$host是ip地址, 直接设置ctx->quick = 1, 表示后续逻辑不需要走dns解析逻辑.

• 如果r->udp_connections 不存在, 返回NGX_NO_RESOLVER, 最终请求返回502.初始化ctx参数

• ctx->type = NGX_RESOLVE_A;

• ctx->handler = ngx_http_upstream_resolve_handler;

• ctx->timeout = clcf->resolver_timeout;

• ngx_resolve_name(ctx)

• 如果 ctx->quick == 1, 直接调用 ctx->handler, 跳过dns解析.

• 否则调用 ngx_resolve_name_locked, 执行dns解析.

• ngx_resolve_name_locked(r, ctx)

1 调用ngx_resolver_lookup_name查找域名节点rn是否在r->name_rbtree缓存节点中, 存在进入(2), 否者进入 (5)

2 判断rn->valid是否过期,没有过期进入(3), 否者进入(4).

3 如果存在 rn->naddrs, 是A记录节点, 循环调用rn->waiting链表上的 ctx->handler, 然后函数返回OK; 如果不存在 rn->naddrs, 表示是CNAME记录节点, 那么递归调用ngx_resolve_name_locked,进入步骤 (1).

4 rn->valid已经过期, 如果存在rn->waiting, 表示已经触发了新的dns请求, 只需要把ctx挂在到链表上, 函数返回NGX_AGAIN. 如果不存在rn->waiting,表示这是域名失效之后的第一个请求, 需要清空上一次dns请求申请的内存, 进入 (6)

5 不存在rn, 表示第一次域名请求, 初始化rn节点, 并加入 r->name_rbtree红黑树.

6 创建域名查询请求 ngx_resolver_create_name_query

7 发送域名查询请求 ngx_resolver_send_query, 并设置dns查询的读事件 uc->connection->read->handler = ngx_resolver_read_response

8 挂载超时事件 ngx_add_timer(ctx->event, ctx->timeout) ctx->event->handler->ngx_resolver_timeout_handler

9 函数结束, 返回NGX_AGAIN.

过程比较复杂,总的来说,当proxy_pass后面是连接的时候,即使不定义upstream,nginx也会隐式的,将proxy_pass后面的url创建一个upstream,由upstream模块进行调用resolver来做域名的解析

解析是在初始化的时候就进行的,首先会根据服务器DNS配置或host配置进行一个缓存队列,队列中缓存的IP及域名对是有过期时间的,过期后清理,重新进行解析

我通过正常的配置,curl请求,反向代理到百度正常,接着我修改我的hosts文件,将百度代理到一个随意的内网地址,再次请求,仍然可以请求到,所以可以证明上面的缓存时间,所以当你更新DNS后,为了让nginx更快更新,需要重启nginx

resolver对于IPv6的配置,默认是开启的,也就是当域名解析到既有ipv4又有ipv6时,都会解析到,官方提供ipv6=on|off,来控制ipv6解析

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 运维研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档