1. 程式人生 > >dns解析相關程式碼分析

dns解析相關程式碼分析

轉載至: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 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::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檔案,將CONFIGproxy.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;
                }
            }
        }
  }