traffic server dns解析相关代码分析

来源:互联网 发布:网络渗透技术 编辑:程序博客网 时间:2024/04/30 03:40

http://blog.chinaunix.net/uid-23242010-id-93354.html    

    TrafficServer提供了DNS解析相关的功能,相关模块为iocore/dns。目前dns模块还有很多问题需要解决[1]。
    首先从配置管理上分析dns模块。以下给出ts配置文件records.config中与dns相关的配置选项:

CONFIG proxy.config.dns.splitDNS.enabled INT 0
CONFIG proxy.config.dns.resolv_conf STRING /etc/resolv.conf

    第一行配置是否启用splitdns解析,默认不启用。第二行配置dns解析文件。dns解析提供了两种功能,一种是根据默认的配置文件(resolv_conf指定)进行dns解析,另一种是提供splitdns解析功能,对应的配置文件为splitdns.config。如需更进一步了解dns以及splitdns配置相关内容,请参考[2]。
    dns针对以上两种解析方式,采用如下设计逻辑进行了实现:通过DNSProcessor+DNSHandler+DNSEntry实现dns解析;通过SplitDNS+SplitDNSConfig+SplitDNSRecord提供额外的splitdns解析功能。
    (1)
    dns解析遵循ts的异步事件模型的设计原理[3],DNSProcessor借助DNSHandler对一个域名进行解析。
    DNSEntry类实现了对一个dns请求的封装。ts内部实现封装了一组与libresolv库相同功能的方法[4],例如ink_res_int等等。
    DNSHandler维护了两个队列:

struct DNSHandler: public Continuation
{
  DNSConnection con[MAX_NAMED];
  Queue<DNSEntry> entries;
};

    1)DNSConnection con[MAX_NAMED],数组中每一个元素代表一个与dns解析服务器的连接。通常ts只使用一个dns连接,只有需要使用roundrobin功能时才会同时连接多个dns解析服务器。以下代码摘自函数DNSHandler::startEvent:

if (dns_ns_rr) {//使用roundrobin
      int max_nscount = m_res->nscount;
      if (max_nscount > MAX_NAMED)
        max_nscount = MAX_NAMED;
      n_con = 0;
      for (int i = 0; i < max_nscount; i++) {
        struct sockaddr_in *sa = &m_res->nsaddr_list[i].sin;
        ip = sa->sin_addr.s_addr;
        if (ip) {
          port = ntohs(sa->sin_port);
          dnsProcessor.handler->open_con(ip, port, false, n_con);
          ++n_con;
          Debug("dns_pas", "opened connection to %d.%d.%d.%d:%d, n_con = %d",DOT_SEPARATED(ip), port, n_con);
        }
      }
      dns_ns_rr_init_down = 0;
    } else {
      dnsProcessor.handler->open_con(ip, port);
      n_con = 1;
    }

    2)Queue<DNSEntry> entries,保存需要发送出去的dns请求。
    根据以上分析,通过DNSHandler::startEvent,与dns解析服务器进行连接。在startEvent函数中,通过下面两行代码异步周期调用DNSHandler::mainEvent:

SET_HANDLER(&DNSHandler::mainEvent);

dnsProcessor.thread->schedule_every(this, DNS_PERIOD);

    通过分析DNSHandler::mainEvent方法,以下几行:

recv_dns(event, e);
if (entries.head)
  write_dns(this);

    通过DNSHandler::recv_dns,遍历DNSHandler维护连接的每一个dns解析服务器,取出dns响应结果。如果DNSHandler维护的DNSEntry队列不为空,则通过static类型的write_dns函数向dns服务器发送dns请求。
    下面分析ts是如何将一个dns请求插入到DNSHandler维护的DNSEntry队列中去的。前面提到,dns解析遵循ts的异步事件模型的设计逻辑,这种设计逻辑通常由Processor调度Handler处理一个Continuation状态序列,在这里也不例外。DNSProcessor提供了一组get方法作域名解析,这些get方法都通过内部调用DNSProcessor::getby方法实现域名解析,以DNSProcessor::gethostbyname为例:

inline Action *
DNSProcessor::gethostbyname(Continuation * cont, const char *name, DNSHandler * adnsH,int timeout)
{
  return getby(name, 0, T_A, cont, 0, adnsH, timeout);
}

    通过分析DNSProcessor::getby方法,其实现主要是创建了一个DNSEntry:

DNSEntry *= dnsEntryAllocator.alloc();

  e->retries = dns_retries;
  e->init(x, len, type, cont, wait, adnsH, timeout);

    DNSEntry::init方法通过构造一个dns请求后,执行:

SET_HANDLER((DNSEntryHandler) & DNSEntry::mainEvent);

    我们分析DNSEntry::mainEvent方法,这段代码中最主要的是以下两行:

dnsH->entries.enqueue(this);
        write_dns(dnsH);

    可以看出,DNSEntry::mainEvent方法的主要作用就是将自己插入到对应的DNSHandler中的DNSEntry队列中去。
    通过以上分析可以看出,dns解析通过DNSProcessor::getby不断处理每一个dns请求,并构造dns消息体插入到DNSHandler对应的DNSEntry队列中去,DNSHandler定期调用DNSHandler::mainEvent方法从dns服务器中取回dns响应,并根据其维护的DNSEntry队列构造新的dns请求发送给dns服务器。
    dns解析是通过DNSProcessor::start方法启用的。该方法首先读取一些配置选项,然后执行DNSProcessor::dns_init加载resolv_conf变量对应的文件(默认为/etc/resolv.conf),最后执行DNSProcessor::open方法,该方法内部调用DNSHandler::startEvent,接下来按照上面描述的流程,一个dns解析服务就启用了。
    (2)
    splitdns是后来的ts开发团队在原有基础上增加的功能,设计逻辑非常混乱,以TrafficServer2.1.4版本为例,整个代码嵌套在了iocore/dns,iocore/hostdb,以及proxy模块中。从功能上来看,目前splitdns的功能还不是很完善,后续需要很多改进,[1]介绍了后续的一些改进方案。以下大体介绍一下splitdns相关的一些关键代码。
    1)SplitDNSConfig通过SplitDNSConfig::startup方法根据配置文件选项选择是否启动splitdns功能,该函数调用SplitDNSConfig::reconfig函数。下面分析reconfig这个函数的内容:

IOCORE_ReadConfigInt32(gsplit_dns_enabled, "proxy.config.dns.splitDNS.enabled");
  if (== gsplit_dns_enabled)
    return;

    以上第一行读取records.config配置文件中的proxy.config.dns.splitDNS.enabled,如果为0,说明不启用splitdns,返回,如果为1,说明启用,函数继续往下执行:

params->m_DNSSrvrTable = NEW(new DNS_table("proxy.config.dns.splitdns.filename",modulePrefix, &sdns_dest_tags));

    这行代码基本上完成了加载splitdns.config以及启用splitdns的所有工作。通过分析DNS_table的类型:

typedef ControlMatcher<SplitDNSRecord, SplitDNSResult> DNS_table;

    ControlMatcher是一个泛型类,一路跟踪其构造函数,直到HostMatcher<Data, Result>::NewEntry
函数体,里面有这么两行关键代码:

Data *cur_d;
errBuf = cur_d->Init(line_info);

    由于这里Data属于SplitDNSRecord类型,查看SplitDNSRecord::Init函数,其参数类型为matcher_line,这个结构体实际上保存的就是splitdns.config文件中一行的内容。分析Init函数,该函数的作用就是针对splitdns.config文件的一行,创建一个DNSHandler,并开启一个线程异步调用DNSHandle::startEvent_sdns函数,通过分析DNSHandler::startEvent_sdns函数,可以看出其功能和DNSHandler::startEvent函数的功能是一样的,可能ts开发人员不希望splitdns提供roundrobin等相关功能,所以额外写了一个与之类似的函数。
    2)通过SplitDNSConfig::startup方法,当在records.config文件中enable splitdns后,splitdns已经开启。我们接着分析SplitDNSRecord这个结构体,可以看出其内部有一个成员:

DNSServer m_servers;

    继续分析DNSServer这个结构体:

struct DNSServer
{
  unsigned int x_server_ip[MAXNS];
  char x_dns_ip_line[MAXDNAME * 2];

  char x_def_domain[MAXDNAME];
  char x_domain_srch_list[MAXDNAME];
  int x_dns_server_port[MAXNS];

  DNSHandler *x_dnsH;
};

     仔细分析SplitDNSRecord::Init函数,可以看出,每一个SplitDNSRecord与splitdns.config文件的一行对应,而SplitDNSRecord包含的DNSServer类型的成员变量的DNSHandler类型成员变量也就与splitdns.config文件的一行绑定在了一起,splitdns.config文件的一行对应的正是一组负责解析一个域名的dns服务器。
    3)SplitDNS这个结构体提供了对外的接口。SplitDNS::getDNSRecord解析参数指向的域名,并返回一个DNSServer,从而也间接得到了一个DNSHandler。将这个DNSHandler作为参数调用DNSProcessor::getby,就可以实现从splitdns模块解析一个域名了。
    从上面的描述中我们可以看出,通过splitdns解析一个域名。只需要首先配置records.config文件,将CONFIG proxy.config.dns.splitDNS.enabled赋值为1;然后通过

SplitDNSConfig::startup();

启用splitdns;再通过


  if(&& NULL == adnsH && SplitDNSConfig::isSplitDNSEnabled()){
        const char *scan = x;
        for(; *scan != '\0' && (ParseRules::is_digit(*scan) || '.' == *scan); ++scan);
        if('\0' != *scan){
            void *pSD = (void *) SplitDNSConfig::acquire();
            if(!= pSD){
                void *pDS = ((SplitDNS *) pSD)->getDNSRecord(x);

                if(!= pDS){
                    adnsH = ((DNSServer *) pDS)->x_dnsH;
                }
            }
        }
  }

获取一个域名对应的splitdns中的DNSHandler(这里x代表一个域名),最后以这个DNSHandler显示调用DNSProcessor::getby函数,就可以实现splitdns解析功能了。
    针对splitdns的功能完善问题,TS-435[5]以及TS-541[6]有更为详细的描述。

[1]https://cwiki.apache.org/confluence/display/TS/HostDBandDNS
[2]http://trafficserver.apache.org/docs/v2/admin/ConfigurationFiles下面splitdns.config
[3]http://trafficserver.apache.org/docs/v2/sdk/CreatingTSPlugins.htmlchapter2
[4]http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=/apis/resmkq.htm
[5]https://issues.apache.org/jira/browse/TS-435?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12932442#action_12932442
[6]https://issues.apache.org/jira/browse/TS-541