Android DNS之DNS引數設定
阿新 • • 發佈:2018-12-11
概述
ConnectivityService會通過netd將DNS引數設定到解析庫的cache中,設定介面是_resolv_set_nameservers_for_net(),後續在DNS查詢過程中,解析庫會從cache中獲取設定的DNS伺服器地址。
資料結構
Android中,將DNS資訊儲存到了resolv_cache_info中,該結構中與DNS有關的資訊如下所示:
struct resolv_cache_info {
//網絡卡的netid
unsigned netid;
//所有的cache_info構成一個列表
struct resolv_cache_info* next;
//設定的DNS伺服器地址的數目,即下面nameservers陣列中有效資料由幾個
int nscount;
//儲存設定的DNS伺服器地址,當前限制最多可以設定4個DNS伺服器地址
char* nameservers[MAXNS];
//轉換後的DNS伺服器地址資訊,用於查詢
struct addrinfo* nsaddrinfo[MAXNS];
//見註釋,DNS伺服器地址每變更一次,該成員的值加1
int revision_id; // # times the nameservers have been replaced
//這兩個引數用於域名搜尋,具體見hostname(7),Android中基本上不使用,可以忽略
char defdname[MAXDNSRCHPATH];
int dnsrch_offset[MAXDNSRCH+1]; // offsets into defdname
};
_resolv_set_nameservers_for_net()
@netid:要設定的網絡卡;DNS伺服器地址的設定都是基於網絡卡的
@servers:DNS伺服器地址,字串格式,最多可以設定4 個
@numservers:要設定的DNS伺服器地址個數,即servers[]陣列的長度
@domains:本地域名,通常為空,用於DNS域名搜尋,Android中基本不適用,可以不關注
@params:DNS快取使用的幾個引數
int _resolv_set_nameservers_for_net(unsigned netid, const char** servers, unsigned numservers,
const char *domains, const struct __res_params* params)
{
char sbuf[NI_MAXSERV];
register char *cp;
int *offset;
struct addrinfo* nsaddrinfo[MAXNS];
//要設定的DNS伺服器地址不能超過MAXNS,當前為4個
if (numservers > MAXNS) {
XLOG("%s: numservers=%u, MAXNS=%u", __FUNCTION__, numservers, MAXNS);
return E2BIG;
}
//下面這段邏輯對要設定的DNS伺服器地址進行一種簡單的校驗,這種校驗只是呼叫getaddinfo()轉換一下而已
// Parse the addresses before actually locking or changing any state, in case there is an error.
// As a side effect this also reduces the time the lock is kept.
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_DGRAM,
//必須是數字型別
.ai_flags = AI_NUMERICHOST
};
//該地址的53號埠是否有對應的服務
snprintf(sbuf, sizeof(sbuf), "%u", NAMESERVER_PORT);
for (unsigned i = 0; i < numservers; i++) {
// The addrinfo structures allocated here are freed in _free_nameservers_locked().
//服務為sbuf,不為空,這會校驗指定的地址的埠53是否存在服務
int rt = getaddrinfo(servers[i], sbuf, &hints, &nsaddrinfo[i]);
//所設定的DNS伺服器地址必須全部正確,只要有一個有錯誤,那麼設定失敗
if (rt != 0) {
for (unsigned j = 0 ; j < i ; j++) {
freeaddrinfo(nsaddrinfo[j]);
nsaddrinfo[j] = NULL;
}
XLOG("%s: getaddrinfo(%s)=%s", __FUNCTION__, servers[i], gai_strerror(rt));
return EINVAL;
}
}
//_res_cache_init()在程序內只執行一次
pthread_once(&_res_cache_once, _res_cache_init);
pthread_mutex_lock(&_res_cache_list_lock);
//如果該netid沒有對應的resolv_cache_info,那麼建立一個
_get_res_cache_for_net_locked(netid);
//獲取該netid對應的resolv_cache_info,如果沒有,那麼在上一步中應該已經建立
struct resolv_cache_info* cache_info = _find_cache_info_locked(netid);
if (cache_info != NULL) {
uint8_t old_max_samples = cache_info->params.max_samples;
//如果有設定params,則使用設定的,否則使用預設的,該引數的使用見“DNS cache機制”
if (params != NULL) {
cache_info->params = *params;
} else {
_resolv_set_default_params(&cache_info->params);
}
//如果要設定的DNS伺服器地址和當前儲存的不相等,那麼需要重新整理
if (!_resolv_is_nameservers_equal_locked(cache_info, servers, numservers)) {
//把舊的DNS伺服器地址資訊釋放掉,然後新增新的,這幾句才是這個函式最核心的內容
_free_nameservers_locked(cache_info);
unsigned i;
for (i = 0; i < numservers; i++) {
cache_info->nsaddrinfo[i] = nsaddrinfo[i];
cache_info->nameservers[i] = strdup(servers[i]);
XLOG("%s: netid = %u, addr = %s\n", __FUNCTION__, netid, servers[i]);
}
//配置的DNS伺服器地址個數
cache_info->nscount = numservers;
// Clear the NS statistics because the mapping to nameservers might have changed.
//清除掉所有的統計資訊
_res_cache_clear_stats_locked(cache_info);
// increment the revision id to ensure that sample state is not written back if the
// servers change; in theory it would suffice to do so only if the servers or
// max_samples actually change, in practice the overhead of checking is higher than the
// cost, and overflows are unlikely
//修正id加1,表示該cache_info結構的DNS資訊發生過一次變更
++cache_info->revision_id;
} else if (cache_info->params.max_samples != old_max_samples) {
// If the maximum number of samples changes, the overhead of keeping the most recent
// samples around is not considered worth the effort, so they are cleared instead. All
// other parameters do not affect shared state: Changing these parameters does not
// invalidate the samples, as they only affect aggregation and the conditions under
// which servers are considered usable.
_res_cache_clear_stats_locked(cache_info);
++cache_info->revision_id;
}
// Always update the search paths, since determining whether they actually changed is
// complex due to the zero-padding, and probably not worth the effort. Cache-flushing
// however is not // necessary, since the stored cache entries do contain the domain, not
// just the host name.
// code moved from res_init.c, load_domain_search_list
//這部分程式碼用於設定DNS搜尋相關的兩個成員,Android中基本不使用,可以忽略
strlcpy(cache_info->defdname, domains, sizeof(cache_info->defdname));
if ((cp = strchr(cache_info->defdname, '\n')) != NULL)
*cp = '\0';
cp = cache_info->defdname;
offset = cache_info->dnsrch_offset;
while (offset < cache_info->dnsrch_offset + MAXDNSRCH) {
while (*cp == ' ' || *cp == '\t') /* skip leading white space */
cp++;
if (*cp == '\0') /* stop if nothing more to do */
break;
*offset++ = cp - cache_info->defdname; /* record this search domain */
while (*cp) { /* zero-terminate it */
if (*cp == ' '|| *cp == '\t') {
*cp++ = '\0';
break;
}
cp++;
}
}
*offset = -1; /* cache_info->dnsrch_offset has MAXDNSRCH+1 items */
}
pthread_mutex_unlock(&_res_cache_list_lock);
return 0;
}
從上面可以看出,每個網絡卡都會有一個resolv_cache_info結構,網絡卡的DNS地址資訊就儲存在該結構的nsaddrinfo、nameservers和nscount中。
DNS引數查詢
在DNS解析過程中,解析庫在進行最終的DNS查詢之前,會向DNS cache查詢在指定網絡卡上面配置的DNS資訊,這個任務由_resolv_populate_res_for_net()完成。
void _resolv_populate_res_for_net(res_state statp)
{
if (statp == NULL) {
return;
}
//獲取鎖
pthread_once(&_res_cache_once, _res_cache_init);
pthread_mutex_lock(&_res_cache_list_lock);
//根據netid查詢resolv_cache_info連結串列
struct resolv_cache_info* info = _find_cache_info_locked(statp->netid);
if (info != NULL) {
int nserv;
struct addrinfo* ai;
XLOG("%s: %u\n", __FUNCTION__, statp->netid);
for (nserv = 0; nserv < MAXNS; nserv++) {
//需要注意的是info->nsaddrinfo儲存的就是設定的DNS地址
ai = info->nsaddrinfo[nserv];
if (ai == NULL) {
break;
}
//地址長度一定夠,因為nsaddrs[0]的型別為union res_sockaddr_union,是所有地址族的地址的最大結構
if ((size_t) ai->ai_addrlen <= sizeof(statp->_u._ext.ext->nsaddrs[0])) {
//ext結構不可能為空,因為在res_init()函式中,該結構是無條件被分配的
if (statp->_u._ext.ext != NULL) {
//將DNS地址拷貝到statp中,這裡需要注意的是永遠都是設定到了ext中,所以statp->nsaddr_list永遠不會被使用
memcpy(&statp->_u._ext.ext->nsaddrs[nserv], ai->ai_addr, ai->ai_addrlen);
//這裡地址族為AF_UNSPEC,很重要,該引數的使用見res_send.c中get_nsaddr()
statp->nsaddr_list[nserv].sin_family = AF_UNSPEC;
} else {
//沒有分配ext結構的情形,那麼只能將其拷貝到statp->nsaddr_list中了,這適用於IPv4
if ((size_t) ai->ai_addrlen
<= sizeof(statp->nsaddr_list[0])) {
memcpy(&statp->nsaddr_list[nserv], ai->ai_addr,
ai->ai_addrlen);
} else {
statp->nsaddr_list[nserv].sin_family = AF_UNSPEC;
}
}
} else {
XLOG("%s: found too long addrlen", __FUNCTION__);
}
}
//設定DNS伺服器地址個數
statp->nscount = nserv;
// now do search domains. Note that we cache the offsets as this code runs alot
// but the setting/offset-computer only runs when set/changed
// WARNING: Don't use str*cpy() here, this string contains zeroes.
//DNS搜尋特性相關,Android中基本不使用,忽略
memcpy(statp->defdname, info->defdname, sizeof(statp->defdname));
register char **pp = statp->dnsrch;
register int *p = info->dnsrch_offset;
while (pp < statp->dnsrch + MAXDNSRCH && *p != -1) {
*pp++ = &statp->defdname[0] + *p++;
}
}
pthread_mutex_unlock(&_res_cache_list_lock);
}