使用情景
前面我們說過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。