Press ESC to close

DNS Search Domain(搜索域)

前言

DNS应该很多人都比较了解,但是对于搜索域可能并不清楚其具体作用,本篇文章以Android T 为例,先介绍Search Domain的概念,然后介绍下安卓上的实现。

Search Domain简介

Search Domain是一项用于简化DNS解析的配置,其用途一般如下

简化域名输入

当用户在浏览器中输入主机名而不是完全限定域名(FQDN: Fully qualified domain name)时,Search Domain可以帮助自动补全域名。

例如,搜索域设置为public1.114dns.com,请求的域名为example时,系统会将其补全为example.public1.114dns.com。加速内部域名访问

对于具有多个内部域名的企业网络,设置搜索域会使得内部域名访问更为容易。用户只需输入主机名,而无需输入完整的内部域名

搜索域的配置通常由DHCP完成,也可由用户手动设置。不同的系统和设备可能设置方式会有差异,但是目前常用的系统都会提供配置入口。

Search Domain技术细节

如下

搜索域配置

可以指定一个或多个,配置方法略。搜索过程

当用户输入一个域名或主机名时,系统的DNS解析器会首先从DNS服务器请求。如果没有找到,系统会根据配置的搜索域开始搜索过程。搜索域附加

搜索过程依次尝试每个搜索域(搜索顺序依赖于我们配置的顺序),直到找到匹配的结果或者遍历完所有的搜索域。结果缓存

一般系统DNS的本地缓存时间都是以DNS response的TTL值为准。

Android T上的实现

具体的DNS请求流程不做详细介绍。

简单来说,是Client端发送DNS请求消息给netd进程中的dnsresolver模块,dnsresolver负责真正的请求。大体函数调用流程如下:

DnsProxyListener::GetAddrInfoHandler::run() -> resolv_getaddrinfo() -> explore_fqdn() -> dns_getaddrinfo() -> res_searchN()

res_searchN()

res_searchN() 通过 res_querydomainN() 方法发起DNS请求,主体代码如下

static int res_searchN(const char* name, res_target* target, ResState* res, int* herrno) {

// ... ...

int got_nodata = 0, got_servfail = 0, tried_as_is = 0;

// ... ...

if (dots >= res->ndots) {

ret = res_querydomainN(name, NULL, target, res, herrno);

if (ret > 0) return (ret);

saved_herrno = *herrno;

tried_as_is++;

}

if ((!dots || (dots && !trailing_dot)) && !isMdnsResolution(res->flags)) {

for (const auto& domain : res->search_domains) {

ret = res_querydomainN(name, domain.c_str(), target, res, herrno);

if (ret > 0) return ret;

// ... ...

}

// ... ...

}

if (!tried_as_is) {

ret = res_querydomainN(name, NULL, target, res, herrno);

if (ret > 0) return ret;

}

// ... ...

}

res_searchN()方法可以分成3段"

判断hostname是否有.号,比如baidu.com或者www.baidu.com

如果是,则先直接请求dns服务器;如果否或者失败那么会继续第2步

注意,这里传递的第二个参数domains为null: res_querydomainN(name, NULL, target, res, herrno);使用搜索域

当然,这里不仅仅判断dot,如果是.local的mDNS,也不会使用搜索域。

如果成功,直接返回,如果失败,继续第3步tried_as_is == 0

使用原始输入再尝试一次。

一般的话,如果我们输入www.baidu.com,第一步就能返回。如果网络不通并且设置了搜索域,在第一步失败后,还会继续执行第二步搜索过程。

res_querydomainN()

res_querydomainN的一些细节

static int res_querydomainN(const char* name, const char* domain, res_target* target, ResState* res,

int* herrno) {

char nbuf[MAXDNAME];

const char* longname = nbuf;

size_t n, d;

assert(name != NULL);

if (domain == NULL) {

// Check for trailing '.'; copy without '.' if present.

n = strlen(name);

if (n + 1 > sizeof(nbuf)) {

*herrno = NO_RECOVERY;

return -1;

}

if (n > 0 && name[--n] == '.') {

strncpy(nbuf, name, n);

nbuf[n] = '\0';

} else

longname = name;

} else {

n = strlen(name);

d = strlen(domain);

if (n + 1 + d + 1 > sizeof(nbuf)) {

*herrno = NO_RECOVERY;

return -1;

}

snprintf(nbuf, sizeof(nbuf), "%s.%s", name, domain);

}

return res_queryN_wrapper(longname, target, res, herrno);

}

当domian不为NULL时,将name和domain进行了拼接,最终Name是.

总结

Andoroid的DNS有超时重试机制,可以dumpsys dnsresolver查看,默认是5s和2次。也就是5s超时,最大尝试2次

假如设置的DNS服务器为 114.114.114.114, Search Domain 为 public1.114dns.com, public2.114dns.com。

当DNS请求www.baidu.com时

首先向114.114.114.114发起DNS请求,name为 www.baidu.com。

如果第一次请求超时,那么会继续尝试第二次;其他原因失败的话会直接返回。搜索过程

由于我们设置了两个搜索域,那么首先向114.114.114.114发起www.baidu.com.public1.114dns.com的DNS请求,如果两次尝试均失败;则继续发起www.baidu.com.public2.114dns.com的DNS请求

整体的耗时是什么样子的,最差情况下

dns_server_counts * timeout * retry_count + search_domain_counts * dns_server_counts * timeout * retry_count

= (1 + search_domain_counts) * dns_server_counts * timeout * retry_count

如果search_domain_counts=2,dns_server_counts=1,timeout=5s,retry_count=2,那么最差情况下的总耗时为 (1+2)25*2=60s.