1,DNS
DNS全称Domain Name System(域名系统),顾名思义,就是把域名解析为指向的IP,让人们通过注册的域名可以方便的访问到网址的一种服务。域名解析就是域名到IP地址的转换过程。域名解析的工作由DNS服务器完成,其过程可简单描述为:域名地址经过DNS服务器解析后,得到对应的IP地址,通过该IP地址访问到服务器获取我们要访问的内容。
2,Android DNS模块
Android Dns模块代码:
bionic/libc/netbsd/*
DNS请求的入口函数:
Int getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res)
3,Android DNS请求机制
3.1,查询方式顺序
getaddrinfo() {
_files_getaddrinfo() //从文件/system/etc/hosts中查询域名地址
_dns_getaddrinfo() //向DNS缓存或者DNS服务器查询域名地址
}
_dns_getaddrinfo() {
_resolv_cache_lookup() //向DNS缓存中查询
send_dg() //像DNS服务器查询
}
3.2,4A和A
Type为4A的dns请求,代表请求域名的IPV6地址
Type为A的dns请求,代表请求域名的IPV4地址
Android设备IPV6单栈的情况(获取了有效的IPV6地址):对域名进行type为4A的dns请求
Android设备IPV4单栈的情况(获取了有效的IPV4地址):对域名进行type为A的dns请求
Android设备IPV6和IPV4双栈的情况(同时获取了有效的IPV6和IPV4地址):对域名分别进行type为4A和A的dns请求,4A的dns请求优先发出
3.3,DNS请求的目的地址
DNS请求的目的地址也就是DNS服务器地址,Android设备的DNS服务器地址根据不同的interface(eth0,wlan0等)来管理,以eth0为例:
情景1:
IPV4先连接成功,IPV6后连接成功
这种情况下IPV4的DNS服务器地址会先设置到DNS模块的DNS地址数组中,IPV6的DNS服务器地址则会滞后,保存顺序如下:
DNS_ADDR_LIST[0] = IPV4.DNS1
DNS_ADDR_LIST[1] = IPV4.DNS2
DNS_ADDR_LIST[2] = IPV6.DNS1
情景2:
IPV6先连接成功,IPV4后连接成功
这种情况下IPV6的DNS服务器地址会先设置到DNS模块的DNS地址数组中,IPV4的DNS服务器地址则会滞后,保存顺序如下:
DNS_ADDR_LIST[0] = IPV6.DNS1
DNS_ADDR_LIST[1] = IPV6.DNS2
DNS_ADDR_LIST[2] = IPV4.DNS1
Android系统DNS模块中默认MAXNS(最多保存DNS服务器地址个数)等于3
也就是说最多向3个DNS服务器发起域名解析请求(前2个DNS服务器都无法成功解析时)
请求过程如下:
{
Send request to DNS_ADDR_LIST[0]
If(ok)
return
else
{
Send request to DNS_ADDR_LIST[1]
If(ok)
return
else
Send request to DNS_ADDR_LIST[2]
}
}
3.4,DNS请求retry和timeout机制
Android dns每次请求等待DNS服务器响应都有timeout时间,超时或者接收到的response不正确则会有retry机制
默认的retry设置是2,也就是说有2次请求机会
每次等待DNS服务器响应的timeout时间基数是5s,实际的timeout时间会根据这个基数和当前请求的dns服务器地址index做一个计算来求出本次等待的timeout时间
4,Android DNS请求流程
业务调用一次getaddrinfo函数进行DNS域名解析流程如下:
getaddrinfo() {
res = _files_getaddrinfo(); // 文件查找,查找成功则getaddrinfo结束
If(res == ok)
return;
_dns_getaddrinfo() { // 向DNS缓存或者DNS服务器查询域名地址
for type in [4A,A]
{
send_request(type) {
res = _resolv_cache_lookup(); //向DNS缓存中查询
If(res == ok)
return; //send_request()结束
send_dg() { //向DNS服务器查询
for retry in [1,2]
{
for dns in [dns1,dns2,dns3]
{
send request to dns
If(ok)
return; //send_dg()结束
}
}
}
}
}
}
}
总结:
业务调用一次getaddrinfo函数进行dns解析请求流程如下:
- 首先从文件/system/etc/hosts中查询域名对应地址,若查找成功则结束
- 文件中未查询成功,则准备发送dns解析请求,如果ipv6地址存在,则优先发送type为4A的请求
- 发送4A请求前先查询DNS缓存,若缓存中查找成功,则结束4A请求,准备进行发送type为A的请求
- 若缓存查找失败,则向DNS服务器列表中的第一个DNS服务器地址发出第一次4A请求,收到服务器response且查询成功则结束4A请求,准备发送type为A的请求
- 若未接收到服务器response或查询失败,则向DNS服务器列表中的第二个DNS服务器地址发出第一次4A请求,收到服务器response且查询成功则结束4A请求,准备发送type为A的请求
- 若DNS服务器列表中3个服务器地址的4A请求都超时或者失败,则重新从第一个服务器地址开始发送第二次4A请求,直到查询成功后结束4A请求,若3个服务器地址的第二次4A请求都未成功,则4A请求查询结果为失败,准备发送type未A的请求
- A请求过程与4A一样的循环方式