前言
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是<name>.<domain>
总结
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.