chromium中DNS主機地址解析過程——基於系統函數查詢的方式

使用情景

前面我們說過FTP協議的網絡資源加載,其中在加載FTP資源的開始就要進行地址解析,對於ftp來說,它處理的狀態是STATE_CTRL_RESOLVE_HOST。

現在我們來分析一下chromium中是如何解析主機地址的,其代碼主要位於net\dns中。
對於dns模塊來說,對外的主要接口是HostResolverImpl::Resolve,我們從這個接口切入,來了解dns解析過程。

上下文調用示例

我們啓動windbg開始調試chromium,加載好調試符號,在HostResolverImpl::Resolve函數上下斷,運行,使得chromium隨意一個主機地址解析產生一箇中斷。當然,網絡資源的請求和加載都是在browser中進行的,我們在主進程中下斷點。

0:000> bp chrome_7feee500000!net::HostResolverImpl::Resolve

如下是中斷後的堆棧情況。

0:029> kc
 # Call Site
00 chrome_7feee500000!net::HostResolverImpl::Resolve
01 chrome_7feee500000!net::SingleRequestHostResolver::Resolve
02 chrome_7feee500000!chrome_browser_net::Predictor::LookupRequest::Start
03 chrome_7feee500000!chrome_browser_net::Predictor::StartSomeQueuedResolutions
04 chrome_7feee500000!chrome_browser_net::Predictor::AppendToResolutionQueue
05 chrome_7feee500000!chrome_browser_net::Predictor::ResolveList
06 chrome_7feee500000!chrome_browser_net::Predictor::DnsPrefetchMotivatedList
07 chrome_7feee500000!chrome_browser_net::Predictor::FinalizeInitializationOnIOThread
08 chrome_7feee500000!base::internal::RunnableAdapter<void (__cdecl disk_cache::SimpleSynchronousEntry::*)(disk_cache::SimpleSynchronousEntry::EntryOperationData const &,net::IOBuffer *,base::Time *,int *)>::Run
09 chrome_7feee500000!base::internal::InvokeHelper<0,void,base::internal::RunnableAdapter<void (__cdecl disk_cache::SimpleSynchronousEntry::*)(disk_cache::SimpleSynchronousEntry::EntryOperationData const &,net::IOBuffer *,base::Time *,int *)> >::MakeItSo
0a chrome_7feee500000!base::internal::Invoker<base::IndexSequence<0,1,2,3,4>,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl disk_cache::SimpleSynchronousEntry::*)(disk_cache::SimpleSynchronousEntry::EntryOperationData const & __ptr64,net::IOBuffer * __ptr64,base::Time * __ptr64,int * __ptr64) __ptr64>,void __cdecl(disk_cache::SimpleSynchronousEntry * __ptr64,disk_cache::SimpleSynchronousEntry::EntryOperationData const & __ptr64,net::IOBuffer * __ptr64,base::Time * __ptr64,int * __ptr64),base::internal::UnretainedWrapper<disk_cache::SimpleSynchronousEntry>,disk_cache::SimpleSynchronousEntry::EntryOperationData,scoped_refptr<net::IOBuffer>,base::Time * __ptr64,int * __ptr64>,base::internal::InvokeHelper<0,void,base::internal::RunnableAdapter<void (__cdecl disk_cache::SimpleSynchronousEntry::*)(disk_cache::SimpleSynchronousEntry::EntryOperationData const & __ptr64,net::IOBuffer * __ptr64,base::Time * __ptr64,int * __ptr64) __ptr64> >,void __cdecl(void)>::Run
0b chrome_7feee500000!base::Callback<void __cdecl(void)>::Run
0c chrome_7feee500000!base::debug::TaskAnnotator::RunTask
0d chrome_7feee500000!base::MessageLoop::RunTask
0e chrome_7feee500000!base::MessageLoop::DeferOrRunPendingTask
0f chrome_7feee500000!base::MessageLoop::DoWork
10 chrome_7feee500000!base::MessagePumpForIO::DoRunLoop
11 chrome_7feee500000!base::MessagePumpWin::Run
12 chrome_7feee500000!base::MessageLoop::RunHandler
13 chrome_7feee500000!base::RunLoop::Run
14 chrome_7feee500000!base::MessageLoop::Run
15 chrome_7feee500000!base::Thread::Run
16 chrome_7feee500000!content::BrowserThreadImpl::IOThreadRun
17 chrome_7feee500000!content::BrowserThreadImpl::Run
18 chrome_7feee500000!base::Thread::ThreadMain
19 chrome_7feee500000!base::`anonymous namespace'::ThreadFunc
1a kernel32!BaseThreadInitThunk
1b ntdll!RtlUserThreadStart

然後顯示出0號堆棧的局部變量的相關情況,查看一下內部的相關信息。這裏省略了部分多餘的信息

0:029> ~~[b2c]s;.frame 0n0;dv /t /v
chrome_7feee500000!net::HostResolverImpl::Resolve:
000007fe`ef17b5f4 4055            push    rbp
00 00000000`0920dcc8 000007fe`ef1c4ae7 chrome_7feee500000!net::HostResolverImpl::Resolve [c:\b\build\slave\win64\build\src\net\dns\host_resolver_impl.cc @ 1930]
@rcx              class net::HostResolverImpl * this = 0x00000000`07225280
@rdx              class net::HostResolver::RequestInfo * info = 0x00000000`0920dde0
@r8d              net::RequestPriority priority = LOWEST (0n1)
@r9               class net::AddressList * addresses = 0x00000000`0a7a2030
.......

0:029> dx -id 0,0 -r1 (*((chrome_7feee500000!net::HostResolver::RequestInfo *)0x920dde0))
(*((chrome_7feee500000!net::HostResolver::RequestInfo *)0x920dde0))                 [Type: net::HostResolver::RequestInfo]
    [+0x000] host_port_pair_  [Type: net::HostPortPair]
    [+0x028] address_family_  : ADDRESS_FAMILY_UNSPECIFIED (0) [Type: net::AddressFamily]
    [+0x02c] host_resolver_flags_ : 0
    [+0x030] allow_cached_response_ : true
    [+0x031] is_speculative_  : true
    [+0x032] is_my_ip_address_ : false
0:029> dx -id 0,0 -r1 (*((chrome_7feee500000!net::HostPortPair *)0x920dde0))
(*((chrome_7feee500000!net::HostPortPair *)0x920dde0))                 [Type: net::HostPortPair]
    [+0x000] host_            : "www.baidu.com" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >]
    [+0x020] port_            : 0x50

當我們查閱相關信息的時候發現,RequestInfo 是我們要向dns查詢的目標,我們查詢的是www.baidu.com的主機地址,端口號是50.

源碼分析

我們來看一下這個分析的源碼,函數中有幾個參數,第一個是請求的目標,這個我們在上面已經看到了,然後是請求的優先級,之後是返回地址,接着是完成回調函數,因爲解析是異步的,還有一個參數是輸出句柄,使得後期可以操作這個查詢請求,最後是網絡日誌相關的打印。

Resolve

主函數

int HostResolverImpl::Resolve(const RequestInfo& info,
                              RequestPriority priority,
                              AddressList* addresses,
                              const CompletionCallback& callback,
                              RequestHandle* out_req,
                              const BoundNetLog& source_net_log) {

.....
  Key key = GetEffectiveKeyForRequest(info, ip_number_ptr, source_net_log);

  int rv = ResolveHelper(key, info, ip_number_ptr, addresses, source_net_log);
  if (rv != ERR_DNS_CACHE_MISS) {
    LogFinishRequest(source_net_log, info, rv);
    RecordTotalTime(HaveDnsConfig(), info.is_speculative(), base::TimeDelta());
    return rv;
  }

  // Next we need to attach our request to a "job". This job is responsible for
  // calling "getaddrinfo(hostname)" on a worker thread.

  JobMap::iterator jobit = jobs_.find(key);
  Job* job;
  if (jobit == jobs_.end()) {
    job =
        new Job(weak_ptr_factory_.GetWeakPtr(), key, priority, source_net_log);
    job->Schedule(false);

    // Check for queue overflow.
    if (dispatcher_->num_queued_jobs() > max_queued_jobs_) {
      Job* evicted = static_cast<Job*>(dispatcher_->EvictOldestLowest());
      DCHECK(evicted);
      evicted->OnEvicted();  // Deletes |evicted|.
      if (evicted == job) {
        rv = ERR_HOST_RESOLVER_QUEUE_TOO_LARGE;
        LogFinishRequest(source_net_log, info, rv);
        return rv;
      }
    }
    jobs_.insert(jobit, std::make_pair(key, job));
  } else {
    job = jobit->second;
  }

  // Can't complete synchronously. Create and attach request.
  scoped_ptr<Request> req(new Request(
      source_net_log, info, priority, callback, addresses));
  if (out_req)
    *out_req = reinterpret_cast<RequestHandle>(req.get());

  job->AddRequest(std::move(req));
  // Completion happens during Job::CompleteRequests().
  return ERR_IO_PENDING;
}

Key

函數中首先根據請求目標計算出key值。其實這個key的定義比較簡單。

typedef HostCache::Key Key;

  struct Key {
    Key(const std::string& hostname, AddressFamily address_family,
        HostResolverFlags host_resolver_flags)
        : hostname(hostname),
          address_family(address_family),
          host_resolver_flags(host_resolver_flags) {}

    bool operator<(const Key& other) const {
      // The order of comparisons of |Key| fields is arbitrary, thus
      // |address_family| and |host_resolver_flags| are compared before
      // |hostname| under assumption that integer comparisons are faster than
      // string comparisons.
      return std::tie(address_family, host_resolver_flags, hostname) <
             std::tie(other.address_family, other.host_resolver_flags,
                      other.hostname);
    }

    std::string hostname;
    AddressFamily address_family;
    HostResolverFlags host_resolver_flags;
  };

其實這個key是一個HostCache中的一個結構,這個結構就是將請求裏面的主要信息包裝一下,主機名,地址族類型,以及解析標誌。使用這個key來唯一標誌dns中一種主機解析job。我們可以調試一下看一下數據.

0:029> dx -id 0,0 -r1 (*((chrome_7feee500000!net::HostCache::Key *)0x920dbf8))
(*((chrome_7feee500000!net::HostCache::Key *)0x920dbf8))                 [Type: net::HostCache::Key]
    [+0x000] hostname         : "www.baidu.com" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >]
    [+0x020] address_family   : ADDRESS_FAMILY_UNSPECIFIED (0) [Type: net::AddressFamily]
    [+0x024] host_resolver_flags : 0

ResolveHelper

這是一個解析的輔助函數。

int HostResolverImpl::ResolveHelper(const Key& key,
                                    const RequestInfo& info,
                                    const IPAddressNumber* ip_number,
                                    AddressList* addresses,
                                    const BoundNetLog& source_net_log) {
  // The result of |getaddrinfo| for empty hosts is inconsistent across systems.
  // On Windows it gives the default interface's address, whereas on Linux it
  // gives an error. We will make it fail on all platforms for consistency.
  if (info.hostname().empty() || info.hostname().size() > kMaxHostLength)
    return ERR_NAME_NOT_RESOLVED;

  int net_error = ERR_UNEXPECTED;
  if (ResolveAsIP(key, info, ip_number, &net_error, addresses))
    return net_error;
  if (ServeFromCache(key, info, &net_error, addresses)) {
    source_net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_CACHE_HIT);
    return net_error;
  }
  // TODO(szym): Do not do this if nsswitch.conf instructs not to.
  // http://crbug.com/117655
  if (ServeFromHosts(key, info, addresses)) {
    source_net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_HOSTS_HIT);
    return OK;
  }

  if (ServeLocalhost(key, info, addresses))
    return OK;

  return ERR_DNS_CACHE_MISS;
}

函數中首先將請求視爲IP地址,來判斷是否可以,然後從Cache中查詢,接着從Hosts文件中查詢,最後查看是否是本機地址。
如果這一切都沒有結果,那麼我們回到Resolve函數中。

HostResolverImpl::Job

創建job

我們通過key檢索Job,看看之前是否已經存在這個解析任務了。

  // Map from HostCache::Key to a Job.
  JobMap jobs_;

  typedef std::map<Key, Job*> JobMap;

在這裏是一個key Job 相關的map數據。
* 如果我們檢索到Job,那麼我們提取出指針,然後向job中增加新的請求。
* 如果我們沒有檢索到job,那麼我們創建一個新的job,並將key,以及優先級等重要信息傳遞給它。然後調度這個job

調度job

  void Schedule(bool at_head) {
    DCHECK(!is_queued());
    PrioritizedDispatcher::Handle handle;
    if (!at_head) {
      handle = resolver_->dispatcher_->Add(this, priority());
    } else {
      handle = resolver_->dispatcher_->AddAtHead(this, priority());
    }
    // The dispatcher could have started |this| in the above call to Add, which
    // could have called Schedule again. In that case |handle| will be null,
    // but |handle_| may have been set by the other nested call to Schedule.
    if (!handle.is_null()) {
      DCHECK(handle_.is_null());
      handle_ = handle;
    }
  }

  PrioritizedDispatcher::Handle PrioritizedDispatcher::Add(
    Job* job, Priority priority) {
  DCHECK(job);
  DCHECK_LT(priority, num_priorities());
  if (num_running_jobs_ < max_running_jobs_[priority]) {
    ++num_running_jobs_;
    job->Start();
    return Handle();
  }
  return queue_.Insert(job, priority);
}

從函數中我們可以獲知,如果當前正在運行的job沒有超過上限,那麼開始運行這個job,否則將job加入到隊列中。

0:029> dv /t 
class net::PrioritizedDispatcher * this = 0x00000000`07225370
class net::PrioritizedDispatcher::Job * job = 0x00000000`0a7a3dc0
unsigned int priority = 1
0:029> dx -id 0,0 -r1 (*((chrome_7feee500000!net::PrioritizedDispatcher *)0x7225370))
(*((chrome_7feee500000!net::PrioritizedDispatcher *)0x7225370))                 [Type: net::PrioritizedDispatcher]
    [+0x000] queue_           [Type: net::PriorityQueue<net::PrioritizedDispatcher::Job *>]
    [+0x020] max_running_jobs_ : { size=5 } [Type: std::vector<unsigned __int64,std::allocator<unsigned __int64> >]
    [+0x038] num_running_jobs_ : 0x1
0:029> dx -id 0,0 -r1 (*((chrome_7feee500000!std::vector<unsigned __int64,std::allocator<unsigned __int64> > *)0x7225390))
(*((chrome_7feee500000!std::vector<unsigned __int64,std::allocator<unsigned __int64> > *)0x7225390))                 : { size=5 } [Type: std::vector<unsigned __int64,std::allocator<unsigned __int64> >]
    [<Raw View>]     [Type: std::vector<unsigned __int64,std::allocator<unsigned __int64> >]
    [capacity]       : 5
    [0]              : 0x6
    [1]              : 0x6
    [2]              : 0x6
    [3]              : 0x6
    [4]              : 0x6
0:029> dx -id 0,0 -r1 (*((chrome_7feee500000!net::PriorityQueue<net::PrioritizedDispatcher::Job *> *)0x7225370))
(*((chrome_7feee500000!net::PriorityQueue<net::PrioritizedDispatcher::Job *> *)0x7225370))                 [Type: net::PriorityQueue<net::PrioritizedDispatcher::Job *>]
    [+0x000] lists_           : { size=5 } [Type: std::vector<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >,std::allocator<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> > > >]
    [+0x018] size_            : 0x0
0:029> dx -id 0,0 -r1 (*((chrome_7feee500000!std::vector<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >,std::allocator<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> > > > *)0x7225370))
(*((chrome_7feee500000!std::vector<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >,std::allocator<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> > > > *)0x7225370))                 : { size=5 } [Type: std::vector<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >,std::allocator<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> > > >]
    [<Raw View>]     [Type: std::vector<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >,std::allocator<std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> > > >]
    [capacity]       : 5
    [0]              : { size=0x0 } [Type: std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >]
    [1]              : { size=0x0 } [Type: std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >]
    [2]              : { size=0x0 } [Type: std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >]
    [3]              : { size=0x0 } [Type: std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >]
    [4]              : { size=0x0 } [Type: std::list<net::PrioritizedDispatcher::Job *,std::allocator<net::PrioritizedDispatcher::Job *> >]

我們運行到這裏,看一下動態調試的數據,看看實際運行的結果,我們可以獲知,目前最大運行job數是6,分5個優先級,每個優先級數目相同,而隊列情況也是空,目前隊列中沒有要處理的請求。

啓動job

  // PriorityDispatch::Job:
  void Start() override {
    ......
    bool system_only =
        (key_.host_resolver_flags & HOST_RESOLVER_SYSTEM_ONLY) != 0;

    // Caution: Job::Start must not complete synchronously.
    if (!system_only && had_dns_config_ &&
        !ResemblesMulticastDNSName(key_.hostname)) {
      StartDnsTask();
    } else {
      StartProcTask();
    }
  }

job的啓動主要是啓動任務,這裏有兩種方法,一種是使用dns客戶端,一種是使用系統函數,如果解析的時候沒有指定使用系統函數,並且dns客戶端已經完全配置,並且主機名不是以”.local” “.local.”結束的時候,使用dns協議的方式來啓動任務。否則使用系統函數來解析。

// HostResolverFlags is a bitflag enum used by host resolver procedures to
// determine the value of addrinfo.ai_flags and work around getaddrinfo
// peculiarities.
enum {
  HOST_RESOLVER_CANONNAME = 1 << 0,  // AI_CANONNAME
  // Hint to the resolver proc that only loopback addresses are configured.
  HOST_RESOLVER_LOOPBACK_ONLY = 1 << 1,
  // Indicate the address family was set because no IPv6 support was detected.
  HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6 = 1 << 2,
  // The resolver should only invoke getaddrinfo, not DnsClient.
  HOST_RESOLVER_SYSTEM_ONLY = 1 << 3
};

上面顯示的是主機解析flags。在作者電腦上目前的調試會話中使用的系統函數請求的方式,所以我們先介紹StartProcTask。

  void StartProcTask() {
    DCHECK(!is_dns_running());
    proc_task_ = new ProcTask(
        key_,
        resolver_->proc_params_,
        base::Bind(&Job::OnProcTaskComplete, base::Unretained(this),
                   base::TimeTicks::Now()),
        net_log_);

    if (had_non_speculative_request_)
      proc_task_->set_had_non_speculative_request();
    // Start() could be called from within Resolve(), hence it must NOT directly
    // call OnProcTaskComplete, for example, on synchronous failure.
    proc_task_->Start();
  }

而在StartProcTask函數,其實內部是創建一個ProcTask,傳遞進去key和完成回調函數,然後讓ProcTask去啓動這個任務。

HostResolverImpl ProcTask

StartLookupAttempt

proc_task_->Start() 內部調用StartLookupAttempt

  void StartLookupAttempt() {
    DCHECK(task_runner_->BelongsToCurrentThread());
    base::TimeTicks start_time = base::TimeTicks::Now();
    ++attempt_number_;
    // Dispatch the lookup attempt to a worker thread.
    if (!base::WorkerPool::PostTask(
            FROM_HERE,
            base::Bind(&ProcTask::DoLookup, this, start_time, attempt_number_),
            true)) {
      NOTREACHED();

      task_runner_->PostTask(FROM_HERE,
                             base::Bind(&ProcTask::OnLookupComplete,
                                        this,
                                        AddressList(),
                                        start_time,
                                        attempt_number_,
                                        ERR_UNEXPECTED,
                                        0));
      return;
    }

    net_log_.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_ATTEMPT_STARTED,
                      NetLog::IntCallback("attempt_number", attempt_number_));

    if (attempt_number_ <= params_.max_retry_attempts) {
      task_runner_->PostDelayedTask(
          FROM_HERE,
          base::Bind(&ProcTask::RetryIfNotComplete, this),
          params_.unresponsive_delay);
    }
  }

從函數中我們可以看出,首先遞增嘗試計數,然後將任務派發到WorkPool工作線程中,如果沒有派遣成功的話,那麼啓動完成函數傳遞錯誤信息。然後接着判斷是否嘗試次數大於最大值,如果沒有,那麼我們派發一個延遲任務,看在規定的時間內是否解析完成,如果沒有,那麼回調我們的函數,通知沒有成功解析,在RetryIfNotComplete函數中,我們根據一些條件來決定是否繼續請求解析。

DoLookup

  void DoLookup(const base::TimeTicks& start_time,
                const uint32_t attempt_number) {
    AddressList results;
    int os_error = 0;
    // Running on the worker thread
    int error = params_.resolver_proc->Resolve(key_.hostname,
                                               key_.address_family,
                                               key_.host_resolver_flags,
                                               &results,
                                               &os_error);

    // Fail the resolution if the result contains 127.0.53.53. See the comment
    // block of kIcanNameCollisionIp for details on why.
    for (const auto& it : results) {
      const IPAddressNumber& cur = it.address().bytes();
      if (cur.size() == arraysize(kIcanNameCollisionIp) &&
          0 == memcmp(&cur.front(), kIcanNameCollisionIp, cur.size())) {
        error = ERR_ICANN_NAME_COLLISION;
        break;
      }
    }

    task_runner_->PostTask(FROM_HERE,
                           base::Bind(&ProcTask::OnLookupComplete,
                                      this,
                                      results,
                                      start_time,
                                      attempt_number,
                                      error,
                                      os_error));
  }

因爲是跨線程執行,單步操作我們不太容易執行到上面的函數中,那麼我們在DoLookup上下斷,然後運行,這樣再次中斷的時候就到了我們的DoLookup函數中了。
此函數運行在工作池內的線程中,這樣做是爲了不堵塞IO線程。使用resolver_proc來解析主機,然後判斷結果,排除掉127.0.53.53地址,因爲這是一個保留地址,然後派發完成任務。

HostResolverProc

最後,調用系統的解析函數。此類針對於系統函數做了一層封裝

int SystemHostResolverCall(const std::string& host,
                           AddressFamily address_family,
                           HostResolverFlags host_resolver_flags,
                           AddressList* addrlist,
                           int* os_error) {
.....
  if (os_error)
    *os_error = 0;

  struct addrinfo* ai = NULL;
  struct addrinfo hints = {0};
......
#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) && \
    !defined(OS_ANDROID)
  DnsReloaderMaybeReload();
#endif
  int err = getaddrinfo(host.c_str(), NULL, &hints, &ai);

......
  *addrlist = AddressList::CreateFromAddrinfo(ai);
  freeaddrinfo(ai);
  return OK;
}

系統函數使用getaddrinfo來獲取地址信息,並把這個信息轉換成AddressList。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章