Bind9源代码分析

Bind9源代码分析

1、串一遍resolver.c和dispacth.c里面的内容

dns_resolver_prime(请求解析器开始一个fetch启动,如果它还没有启动。一些启动判断,然后进行了rdataset初始化,创建fetch)

–>dns_resolver_createfetch(创建或从res桶区中获取一个fctx, 并为其新fetch绑定事件)

–>fctx_start(作为fctx的绑定控制事件,发送给task处理,负责正常的fctx启动)

–>fctx_try(找到一个不为空的,且未超过配额的addrinfo)

–>fctx_query(初始化一个resquery)

2、从main开始走流程(主要为了找到从cache中拿数据,这里只标出了函数,但是没有把参数加进来,加红的是我认为比较重要的函数)

named/main.c

1、main

函数功能:主函数,发起启动

流程:获取规范程序名称,系统初始化,进行dns\dst\isccc result的注册,解析命令行,调用setup(),启动运行(do while,用isc_app_run()结果来判断结束或重新加载),最后是cleanup())

2、setup

函数功能:必要的检查、设置和记录,启动类库守护进程,创建task\timer\socket管理器,创建server,根据命令行选择修改server上下文。

流程:获取用户和组信息,初始化时间转换信息,检查cpu个数,切换到最小权限,初始化日志系统,设置默认日志的默认配置,启动守护进程,开始一个isc library 应用,写日志,获取初始资源限制,针对系统,提高限制,更改目录,记录服务启动时间,创建task\timer\socket管理器,调用 named_server_creat()e创建server,根据命令行选择,修改server上下文

3、named_server_create

函数功能:使用默认设置创建服务器对象。

流程:从内存上下文中分配地址给server,初始化derver结构,设置服务器任务,默认设置创建一个server上下文,有GeoIP必须在interface manager创建个之前进行初始化,任务绑定关闭时调用函数shutdown_server。isc_app_onrun绑定run_server()函数。服务器计时器和接口、心跳服务时间间隔初始化。zone管理器的创建和初始化。Server的hostname和version设置,zone\resolver\sock统计器创建和设置,创建控制命令响应,初始化dispatches\statschannels\cachelist list。Key相关的初始化。

4、run_server

函数功能:运行服务,创建关键管理器,加载配置和zones。

流程:创建dispatch_manager,并设置统计器。创建interfacemgr.创建个interface等的计时器。创建默认和额外的配置解析器。load_configuration载入配置。load_zones()载入zones。

5、load_configuration

函数功能:加载配置。

流程:从server->interfacemgr中获取acl环境,初始化viewlist\builtin_viewlist\cachelist\altsecrets list,创建ACL配置上下文,关闭所有dyndb实例,解析全局默认伪配置文件。用新配置代码来解析配置文件,记录日志信息。检查解析正确性,创建属性map,用于解析默认值。 Dnssec处理。确保对配置数据的独占,设置处理限制,检查处理锁文件,检查系统允许的最大打开套接字数目。获得资源的当前限制。配置多样的server选项,配置配额。设置黑名单。设置保持响应顺序。配置统计服务。配置tcp相关。配置UDP相关。配置EDNS UDPsize。配置zonemgr。确定用于监听传入连接的端口。确定默认DSCP。设置监听队列深度,最小为10.根据“listen-on”声明配置接口管理。这里对IPV6和IPV4不同。ns_interfacemgr_scan()扫描操作系统网络接口列表,并发现新接口是,创建个监听器。对于消失的interface关闭sockets。这应该在服务器启动时调用一次,然后周期性调用。检查named能够在至少一个接口进行TCP监听。安排扫描间隔。配置自动扫描。配置心跳计时器。写PID文件。配置会话秘钥。视图、区域创建和配置。内置视图和配置视图的合并。加载TKEY信息。绑定控制端口。放弃root权限。检查工作目录是否可写。配置日志心跳,设置日志记录标志的默认值。配置各种file,交换altsecrets列表。

lib/ns/interfacemgr.c

6、ns_interfacemgr_scan

函数功能:扫描操作系统网络接口列表,并在发现新接口时创建监听器。 对于消失的interface关闭sockets。这应该在服务器启动时调用一次,然后根据named.conf中的’interface-interval’选项定期调用。

流程:开始独占任务,检查是否成功。调用ns_interfacemgr_scan,如果独占,接收独占

7、ns_interfacemgr_scan

函数功能:扫描操作系统网络接口列表,并在发现新接口时创建监听器。

流程:mgr验证,调用do_scan,如果不成功不会进行清查操作,如果成功进行清除操作,清除旧的接口。如果没有在任何接口上监听,记录日志。

8、do_scan

函数功能:扫描操作系统网络接口列表,并在发现新接口时创建监听器。

流程:探测支持的接口,设置支持选项IPV4和IPV6不同。创建一个迭代器,用于遍历操作系统的网络接口。遍历,获得当前迭代到的网络接口,获取网络簇,根据支选项做操作。根据网络簇,选择监听的list,遍历list,构造ip/port组合,查看地址是否与listen-on语句匹配。如果没有,忽略这个接口。如果匹配数量不大于0,进入下一次迭代。如果匹配数量大于0,根据参数,开始初始化监听。使用额外的监听列表调整接口时,查看地址是否与额外列表匹配。如果是这样,且通配符即可被覆盖,要明确监听地址。做sockaddr地址格式转换(人类可读的转换为字符串数组),记录日志。ns_interface_setup创建初始化接口。(在没有找到匹配接口的情况下,创建初始化新的接口)

9、ns_interface_setup

函数功能:创建接口,接口的启动

流程:创建interface, ns_interface_listenudp 在给定的IP地址和端口上监听UDP事件,如果失败清空interface. 如果有tcp选项,ns_interface_accepttcp,如果失败我们目前没有办法轻松停止调度服务,因此我们目前正在返回 ISC_R_SUCCESS(即使TCP创建失败,UDP内容仍然有效)。 这将在稍后修复。

(这里只看了udp)

10、ns_interface_listenudp

函数功能:在给定的IP地址和端口上监听UDP事件

流程:设置属性值,设置ifp的udpdispatch数量。为ifp分配udpdispatch数量的dns_dispatch,调用,dns_dispatch_getudp_dup,创建udpdispatch数量的ns_client对象,由其来处理给定网络接口上客户端传入的DNS请求。ns_clientmgr_createclients

11.1、dns_dispatch_getudp_dup

函数功能:获得UDP dispatch。如果存在由dns_dispatchmgr_find发现的现有的dns_dispatch_t,则附件到现有的dns_dispatch_t,否则创建新的UDP分配

流程:参数检查。分配内存池,分配qid,判断是否已经有一个匹配的dispatcher,有的话找到,引用计数+1,没有的话,创建一个udp dispatcher dispatch_createudp

12.1、dispatch_createudp

函数功能:创建udp

流程:内存中分配disp,设置属性。设置disp套接字类型为udp,如果不是独占套接字,获取udp套接字,get_udpsocker(里面调用了open_socket),如果是独占的套接字,获取协议簇的通配符地址,如果其与localaddr不等,开启套接字open_socket。disp的port_table初始化,内存池中分配disp_portpool。为disp设置获取的sock,设置任务数,创建个任务数量的任务,设置任务名称,分配ctlevent事件,里面班导了destory_disp函数,用来销毁disp。分配disp->sepool,将disp加入到mgr->list。记录日志。

13.1、dispatch_createudp

函数功能:开启socket

流程:如果传入的参数socket不为空,isc_socket_open(socket),如果是重复的socket,会调用isc_socket_dup,如果其他情况创建一个新的套接字。isc_socket_open里面会调用系统的底层函数。

11.2、ns_clientmgr_createclients

函数功能:创建最多’n’个客户端,监听接口’ifp’。 如果’tcp’是ISC_TRUE,则客户端将侦听TCP连接,否则将侦听UDP请求

流程:验证manager是有效的,for循环调用get_client创建disp数量个client

12.2、get_client

函数功能:获取client

流程:manager检验,尝试从回收中找到一个client,如果失败,则client_createt。创建成功往manager->clients里面添加client,设置client属性,将client->ctlevent(控制事件)发送到它的task

13.2、client_create

函数功能:创建个client

流程:获取client的内存上下文,从上下文中分配内存。创建task.。设置client属性,创建message,创建套接字event,client->sendevent,绑定函数client_senddone(用来处理response的应该)。如果创建失败,清空message,分配recvbuf,如果分配失败清空sendevent。创建套接字事件,client->recvevent,绑定函数ns__client_request(处理客户请求)。如果没有创建成功,清空recvbuf。设置clietn其他属性,初始化其控制事件,client->ctlevent,绑定函数client_start。创建有效的客户端之前,调用客户端的init例程,绑定task关闭的时候,执行的函数client_shutdown.

14.2、client_start

函数功能:作为启动过程的一部分,client的task已经收到了client的control event。

流程:断言task匹配,断言task未被使用。断言控制数量计数为1,计数减减,检查client状态。如果是tcp,如果是流水线方式client_read,不是的话client_accept。如果是udp, client_udprecv

15.2、client_udprecv

函数功能:接收udp

流程:调用isc_socket_recv2,其将从client->udpsocket收到的内容存储到client->recvbuf中,并交由其client->recvevent事件处理,该事件绑定的处理函数为ns__client_request(处理客户请求)

16.2、ns__client_request

函数功能:处理来自套接字的传入请求事件(UDP情况)或tcpmsg(TCP情况)。

流程:client有效性检查。请求计数+1。根据udp和tcp的情况不同,以不同的方式来获得内容buffer。读取buffer used size,client状态检查,设置client状态,获取请求时间,获得远端peer的netaddr,记录请求类型,为udp请求做黑名单acl检查。静默丢弃当前的多播请求,读取message id和flag。客户端对象处理请求而不是响应,如果是udp响应,将其转发给调度程序。dns_dispatch_importrecv,如果是tcp响应,丢弃。更新统计计数器。如果是request,解析dns_message_parse,如果失败发送失败响应,如果是OPTERR,message里面添加opt,记录解析错误日志。流水线TCP查询的处理。增加操作的统计计数。处理edns,如果支持edns,获取opt,dns_message_getopt,如果opt!=null,并且没有放弃opt,进行process_opt(这个可能很重要,还没有看)。如果message->rdclass异常,做格式化回复。记录错误日志。获得目的地址,获得目的套接字,查找一个匹配的视图matchingview,如果没有成功,进行虚拟TSIG的验证产生。检查签名。确定客户端师范可以使用递归服务。调整该客户端的最大UDP响应大小。调度请求。这里只要是正常情况都调用ns_query_start

17.2、ns_query_start

函数功能:开始查询

流程:验证client,测试模块。确保适当的清理。根据message flag设置client属性,如果没有缓存,关闭缓存支持和递归,如果不允许客户递归,关闭递归。检查多个问题查询。获取question name。记录查询日志。检查IXFR和AXFR等元查询。打开DNSKEY和DS查询的最小响应。打开EDNS/UDP size的最小响应。根据客户端查询设置,设置属性。格式化msg中的查询回复。设置AD。将qclient附加到client。查询初始化和启动,调用query_setup

18.2、query_setup

函数功能:为client的当前查询设置查询处理,初始化查询上下文,检查SERVFALL缓存,然后将处理切换到ns__query_start()。仅从ns_query_start()调用此方法,以便首次开始查询. 重新启动现有查询(例如,处理CNAME查找)是通过使用相同的查询上下文再次调用ns__query_start()来完成的。 递归的恢复由query_resume()处理。

流程:初始化查询上下文,qctx_inti。如果是SIG查询,将会迭代节点。qctx设置为已经初始化了的.检查SERVFAIL cache。调用ns__query_start

19.2、ns__query_start

函数功能:客户端查询或链接查询的起点。首先由query_setup()调用,然后根据需要再次调用以跟随CNAME链。确定要搜索的权威数据库,然后将处理切换到query_lookup()。

流程:qctx的属性赋值。日志记录。设置根秘钥标记处理。如果“qtype”类型的rdata应出现在区域切割的父级中并且query.qname,和dns_rootname不等,设置get db不精确,获取qctx->db,。query_getdb 。处理非递归的,QTYPE=DS的查询,获取zonedb,。query_getzonedb。如果获取成功,我们是权威的QNAME的权威,附件相关的zone到查询上下文,设置结果为success,否则不是权威,clean up然后离开。如果没有找到可以回答查询的数据库,使用REFUSED或者SERVFAL进行响应。具体取决于query_getdb的结果。如果找到了一个数据库,可以从中回答查询。如果使用权威数据准备答案,更新相关查询的上下文标志。附加到将准备答案的数据库,更新查询统计信息。最后调用。quert_lookup,做本地数据库的查找。

20.2、quert_lookup

函数功能:在权威数据库或缓存数据库中执行本地数据库查找。 如果无法回答,调用query_done,否则将处理交给query_gotanswer()

流程:qctx上下文状态变为LOOKUP_BEGIN,client信息函数初始化,client信息初始化,分配资源,从池中获取名称缓冲区,或者根据需要分配缓冲区。分配rdataset缓冲区,在database中找到一个answer,设置必要属性。调用dns_db_findext(从db中获取找到answer的地方,这里还没有看,要看)。修复fname和sigrataset,根据result中的结果更新缓存统计信息。如果是要陈旧的,做一系列判断和值设置,最后记录日志,解析失败,陈旧的数据。最后调用query_gotanswer

21.2、query_gotanswer

函数功能:在执行数据库查找或从递归返回后继续,并根据搜索结果调出下一个函数。

流程:如果结果不成功,调用query_done。做检查。如果需要,通过返回SERVFAIL来处理特殊的root-key-sentinel-is-ta-和root-key-sentinel-not-ta- 。如果result:

Success 调用query_prepresponse

DNS_R_GLUE、DNS_R_ZONECUT。qctx->authoritative = ISC_FALSE,调用query_prepresponse

ISC_R_NOTFOUND,query_notfound(qctx)

DNS_R_DELEGATION,query_delegation(qctx)

DNS_R_EMPTYNAME,,query_nodata(qctx, DNS_R_EMPTYNAME)
…….

22.2.1、query_notfound

函数功能:处理在数据库查找中找不到名称的情况。 从query_gotanswer()调用。 如果合适,将处理传递给query_delegation()以获取根引用。

流程:如果缓存甚至没有根NS,尝试从hints DB中获取,如果有hints,初始化clientinfomethods以及info,调用dns_db_findext。如果没有hints,result=false。如果不成功result!=success,清楚无意义的根提示,如果可以递归,调用query_recurse,如果成功设置属性,最后调用query_done.如果不可以递归调用query_done。

23.2.1、query_recurse

函数功能:准备客户端以进行递归,然后创建解析器提取,并将事件回调设置为fetch_callback()。 之后,我们终止查询的这个阶段,并在递归完成时使用新的查询上下文继续。

流程:检查先前查询中的递归参数以查看它们是否匹配。 如果没有,请更新递归参数并继续。这里主要是避免递归环。递归参数更新recparam_update。如果不是继续,递归统计计数+1。。如果client->recursionquota==NULL,从依附其到client->sctx->recursionquota。配额统计计数。如果是软配额,如果当前时间不为0,记录日志信息。杀死最旧的递归查询。result=SUCCESS如果是配额,如果当前时间不为0,记录日志信息。杀死最旧的递归查询如果result=success,并且client没有致命问题,且不是TCP请求,替换client,如果替换失败,记录日志,取消配额依附,统计计数增加。如果result!=success,返回result。调用ns_client_recursing(client); 将客户端添加到递归列表的末尾。调用解析器,分配rdataset缓冲区,如果需要DNSSEC做特殊的处理。调用dns_resolver_createfetch(递归响应一个请求)。我们现在正在等待一个获取事件。 在收到所有事件之前,关闭的客户端不会被销毁。

和缓存相关的重点函数:cache_find

先根据name找到node,然后遍历node上的rdataset,根据传入的type(message QUESTION部分获取来的),来找到想要的答案。如果找到最后要找的数据是以rdataset形式来返回的。

猜你喜欢

转载自blog.csdn.net/daihanglai7622/article/details/84789623