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

你可能感兴趣的:(Android,Android,libc中dns部分源码分析)