前言
一次排查接口404问题,引伸的Android DNS解析过程,简单分析总结一下
1、首先明白DNS解析流程
- 操作系统检查自身本地的hosts文件是否有这个网址的映射关系,如果有,直接返回完成域名解析如果hosts文件没有这个域名映射,则查找本地dns解析器缓存,如果有映射关系则完成域名解析
- 如果hosts和本地dns缓存都没有映射关系,则查找TCP/IP中的首选dns服务器(本地dns服务器),收到查询时,如果查询的资源在本地配置区域中,则返回解析地址给客户机,完成域名解析。
- 如果不在本地dns服务器区域解析,但是该服务器缓存了相关的信息,则从缓存中调用这个映射关系,完成解析,但是这个解析不具备权威性
- 如果本地dns服务器本地区域文件与缓存都解析失败,则根据本地dns服务器的设置进行查询,如果没有启用转发模式,本地dns就把请求发给13台根dns,根dns判断这个域名所述的顶级域名服务器,并返回负责该顶级域名的服务器的一个IP地址。本地dns服务器收到ip信息后联系对应的顶级域的这台服务器。这台负责顶级域的服务器收到请求后,如果自己无法解析,它会找到下一集dns的地址并返回给本地dns服务器。当本地dns服务器收到这个地址后,就会取新的服务器上查询一直重复这个动作,直到找到主机
- 如果使用了转发模式,此dns服务器会把请求转发至上一级dns服务器,由上一级服务器进行解析如果也不能解析则继续转发到上机,知道找到能够解析的服务器
2、Java DNS查询内部实现
在Android的DNS操作API同样是Java的InetAddress
来实现的。
jshell> InetAddress.getByName("baidu.com").getHostAddress()
$1 ==> "220.181.38.148"
jshell> InetAddress.get
getAllByName( getByAddress( getByName( getLocalHost() getLoopbackAddress()
jshell> InetAddress.getAllByName("baidu.com")
$2 ==> InetAddress[2] { baidu.com/220.181.38.148, baidu.com/220.181.38.251 }
域名反查,百度IP还是返回IP,DNS欧凯
InetAddress[] addresses = InetAddress.getAllByName("8.8.8.8");
addresses[0].getHostName();
$6 ==> "dns.google"
打开IDE,看看源码和doc,也很简单明了。
nameServices 是真正的DNS查询;并依次解析
DNSNameService 是默认查询实现,通过spi, jndi 方式可以指定DNS服务器,不过只限Java
/**
* 获取DNS服务器信息
*
* @param domain 要获取DNS信息的域名
* @param provider DNS服务器
* @param types 信息类型 "A"(IP信息),"MX"
* @param timeout 请求超时
* @param retryCount 重试次数
* @return 所有信息组成的数组
* @throws NamingException
*/
@SuppressWarnings("rawtypes")
public static ArrayList<String> getDNSRecs(String domain, String provider,
String[] types, int timeout, int retryCount) throws NamingException, NamingException {
ArrayList<String> results = new ArrayList<String>(15);
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial",
"com.sun.jndi.dns.DnsContextFactory");
//设置域名服务器
env.put(Context.PROVIDER_URL, "dns://" + provider);
// 连接时间
env.put("com.sun.jndi.dns.timeout.initial", String.valueOf(timeout));
// 连接次数
env.put("com.sun.jndi.dns.timeout.retries", String.valueOf(retryCount));
DirContext ictx = new InitialDirContext(env);
Attributes attrs = ictx.getAttributes(domain, types);
for (Enumeration e = attrs.getAll(); e.hasMoreElements(); ) {
Attribute a = (Attribute) e.nextElement();
int size = a.size();
for (int i = 0; i < size; i++) {
results.add((String) a.get(i));
}
}
return results;
}
/**
* 获取域名所有IP
*
* @param domain 域名
* @param dnsServers DNS服务器列表
* @param timeout 请求超时
* @param retryCount 重试次数
* @return
*/
public static Set<String> getAllIP(String domain, String[] dnsServers,
int timeout, int retryCount) {
Set<String> ips = new HashSet<String>();
for (String dnsServer : dnsServers) {
List<String> ipList;
try {
ipList = getDNSRecs(domain, dnsServer, new String[]{"A"},
timeout, retryCount);
} catch (NamingException e) {
continue;
}
ips.addAll(ipList);
}
return ips;
}
3、Android DNS查询内部实现
追踪InetAddress
分析,是netbsd 来解释DNS的,Libcore.os.getaddrinfo 为主要调用函数
Android系统采用的是一个从BSD继承而来的标准的系统函数库bionic
bionic/
|——libc//C库
|——libstdc++ //C++实现库
|——libthread_db //线程库
Android DNS 代码都在bionic/libc/netbsd中、(虽然netbsd 是个废弃的项目,但dns功能部分代码被 Android用上了, 真牛逼,代码晦涩难懂)。想了解的同学,查查源码,这里推荐不错的文章Android okhttp3 DNS 底层实现追踪
4、网络优化优化HTTPDNS
我们知道DNS解析通常采用的UDP。UDP基于无连接传输,所以传输效率高。
TCP响应时间=TCP连接时间 + DNS查询时间;
UDP响应时间=DNS查询时间;移动解析HTTPDNS 则基于 HTTP 协议向xx云的 DNS 服务器发送域名解析请求,替代了基于 DNS 协议向运营商 Local DNS 发起解析请求的传统方式,可以避免 Local DNS 造成的域名劫持和跨网访问问题,解决移动互联网服务中域名解析异常带来的困扰。更有效地保障您的业务正常,避免移动互联网中的劫持、跨网域名解析错误等问题。
安卓上要实现自己DNS解析,工作量还不少,建议看看dnsjava,minidns 这些实现库
基于HTTPDNS的+OKHTTP,倒是很舒服就能接入成功,但是我们最好了解整个过程。
简化
- 域名对应的IP地址
- OKHTTP DNS接口,返回
InetAddress[]
对象
byte[] bytes = textToNumericFormatV4("220.181.38.251");
try {
InetAddress byAddress = Inet4Address.getByAddress("baidu.com", bytes);
System.out.println(byAddress instanceof Inet4Address);
} catch (UnknownHostException e) {
e.printStackTrace();
}
如果不是OKHTTP,可以参考阿里云的解决方案Android端HTTPS(含SNI)业务场景:IP直连方案
另外,aliyun httpdns android-sdk ,是个HTTPDNS 实现很好的参考sdk源码,可见这块优化,必须采用成熟的方案。
常用共有云DNS
名称 | 首选地址 | 备选地址 |
114 DNS | 114.114.114.114 | 114.114.115.115 |
阿里DNS | 223.5.5.5 | 223.6.6.6 |
百度DNS | 180.76.76.76 |
参考网站
Android okhttp3 DNS 底层实现追踪
在 Android 上解析 DNS SRV 记录的轻量级方法