1. 程式人生 > >Android DNS之DNS引數設定

Android DNS之DNS引數設定

概述

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);
}