前言

一次排查接口404问题,引伸的Android DNS解析过程,简单分析总结一下

1、首先明白DNS解析流程

  1. 操作系统检查自身本地的hosts文件是否有这个网址的映射关系,如果有,直接返回完成域名解析如果hosts文件没有这个域名映射,则查找本地dns解析器缓存,如果有映射关系则完成域名解析
  2. 如果hosts和本地dns缓存都没有映射关系,则查找TCP/IP中的首选dns服务器(本地dns服务器),收到查询时,如果查询的资源在本地配置区域中,则返回解析地址给客户机,完成域名解析。
  3. 如果不在本地dns服务器区域解析,但是该服务器缓存了相关的信息,则从缓存中调用这个映射关系,完成解析,但是这个解析不具备权威性
  4. 如果本地dns服务器本地区域文件与缓存都解析失败,则根据本地dns服务器的设置进行查询,如果没有启用转发模式,本地dns就把请求发给13台根dns,根dns判断这个域名所述的顶级域名服务器,并返回负责该顶级域名的服务器的一个IP地址。本地dns服务器收到ip信息后联系对应的顶级域的这台服务器。这台负责顶级域的服务器收到请求后,如果自己无法解析,它会找到下一集dns的地址并返回给本地dns服务器。当本地dns服务器收到这个地址后,就会取新的服务器上查询一直重复这个动作,直到找到主机
  5. 如果使用了转发模式,此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解析,工作量还不少,建议看看dnsjavaminidns 这些实现库

基于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

参考网站

cloudflare&DNS

一文看懂网络七层协议/OSI七层模型

cloudflare&DNS

diggui

windows & linux dig download

ping & net

Java DNS查询内部实现

jndi

Local Managed DNS (Java)

dnsjava

Android okhttp3 DNS 底层实现追踪

在 Android 上解析 DNS SRV 记录的轻量级方法

DNS : Java Glossary

minidns

HTTPDNS