Android DNS之DNS参数设置

ConnectivityService通过netd使用_resolv_set_nameservers_for_net()将DNS参数设置到解析库,DNS信息存储于resolv_cache_info结构中,解析库在查询时先从cache获取DNS服务器地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

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 设备上清除 DNS 缓存 在 Android 系统中,DNS 缓存可以通过多种方式被管理和清除。以下是几种常见的方法: #### 方法一:通过命令行工具清除 DNS 缓存 对于开发者模式启用的设备,可以利用 `adb` 命令来操作系统的网络设置并间接清除 DNS 缓存。 运行以下 ADB 命令可重置网络配置: ```bash adb shell settings put global captive_portal_server google.com ``` 此命令会触发系统重新加载网络状态,从而清空部分缓存数据[^1]。 另外,在某些版本中可以直接尝试重启 Network Service 来达到目的: ```bash adb shell stop netd && adb shell start netd ``` #### 方法二:修改应用程序层面的行为 如果目标仅限于 Java 层面的应用程序内部,则可通过强制刷新 InetAddress 的实现机制完成清理工作。例如下面这段代码展示了如何手动释放已有的地址解析记录: ```java import java.net.InetAddress; import java.util.Collections; public class DnsClearExample { public static void main(String[] args) throws Exception { // 获取本地主机名对应的 IP 地址对象列表 Collections.list(InetAddress.getAllByName("example.com")); System.setProperty("networkaddress.cache.ttl", "0"); // 设置 TTL 为零秒 // 再次查询时不会命中之前的缓存条目 Collections.list(InetAddress.getAllByName("example.com")); } } ``` 注意这里调整的是 JVM 参数 networkaddress.cache.ttl 和 networkaddress.cache.negative.ttl ,分别控制正向与反向查找的有效期长度(单位均为秒)。将其设成负数或者很小正值即可让后续请求忽略已有存储项[^2]。 #### 方法三:针对特定 API 版本的操作 自 Android P (API level 28),官方引入了一个新的类 android.net.DhcpResults 。虽然它主要用于 DHCP 租约管理而非单纯处理域名映射关系,但我们仍然可以从其返回结构里找到一些线索用于辅助调试或验证实际效果。更重要的是,随着架构演进到更高层次之后,底层 libcore/bionic 部分的功能逐渐被淘汰替换成了更现代化的设计方案——即前面提到过的 Netd Resovler 组件体系[^3]。 因此,当面对较新型号终端的时候,可能需要额外关注这些变化所带来的影响以及相应的适配策略。 ---
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值