HttpDNS功能说明及实现

作者: 西域小码 | 来源:发表于2017-10-25 15:44 被阅读2204次

    功能说明

    HTTPDNS使用HTTP协议进行域名解析,代替现有基于UDP的DNS协议,域名解析请求直接发送到阿里云的HTTPDNS服务器,从而绕过运营商的Local DNS,能够避免Local DNS造成的域名劫持问题和调度不精准问题。

    功能 说明
    防劫持 绕过运营商Local DNS,避免域名劫持,让每一次访问都畅通无阻。
    精准调度 基于访问的来源IP,获得最精准的解析结果,让客户端就近接入业务节点。
    0ms解析延迟 通过热点域名预解析、缓存DNS解析结果、解析结果懒更新策略等方式实现0解析延迟
    快速生效 避免Local DNS不遵循权威TTL,解析结果长时间无法更新的问题
    降低解析失败率 有效降低无线场景下解析失败的比率

    防劫持

    HTTPDNS代替了传统的LocalDNS完成递归解析的功能,基于HTTP协议的设计可以适用于几乎所有的网络环境,同时保留了鉴权、HTTPS等更高安全性的扩展能力,避免恶意攻击劫持行为。

    精准调度

    传统域名解析的调度精准性问题,本质根源在于Local DNS的部署和分配机制上。由于碎片化的管理方式,这些环节的服务质量同样很难得到保障。HTTPDNS在递归解析实现上优化了与权威DNS的交互,通过<a name="https://datatracker.ietf.org/doc/rfc7871">edns-client-subnet</a>协议将终端用户的IP信息直接交付给权威DNS,这样权威DNS就可以忽略Local DNS IP信息,根据终端用户的IP信息进行精准调度,避免Local DNS的坐标干扰

    DNS解析0延迟:

    • 构建客户端DNS缓存;

        通过合理的DNS缓存,我们确保每次网络交互的DNS解析都是从内存中获取IP信息,从而大幅降低DNS解析开销。根据业务的不同,我们可以
        制订更丰富的缓存策略,如根据运营商缓存,可以在网络切换的场景下复用已缓存的不同运营商线路的域名IP信息,避免网络切换后进行链
        路重选择引入的DNS网络解析开销。另外,我们还可以引入IP本地化离线存储,在客户端重启时快速从本地读取域名IP信息,大幅提升首页
        载入效率。
      
    • 热点域名预解析;

        在客户端启动过程中,我们可以通过热点域名的预解析完成热点域名的缓存载入。当真正的业务请求发生时,直接由内存中读取目标域名的IP
        信息,避免传统DNS的网络开销。
      
    • 懒更新策略;

        绝大多数场景下业务域名的IP信息变更并不频繁,特别是在单次APP的使用周期内,域名解析获取的IP往往是相同的(特殊业务场景除外)。
        因此我们可以利用DNS懒更新策略来实现TTL过期后的DNS快速解析。所谓DNS懒更新策略即客户端不主动探测域名对应IP的TTL时间,当业务
        请求需要访问某个业务域名时,查询内存缓存并返回该业务域名对应的IP解析结果。如果IP解析结果的TTL已过期,则在后台进行异步DNS网
        络解析与缓存结果更新。通过上述策略,用户的所有DNS解析都在与内存交互,避免了网络交互引入的延迟。
      

    实现方案

    服务端:

    服务端提供API接口,app端直接通过ip地址访问,ip地址可以有多个

    请求方式:HTTP GET

    URL参数说明:

    名称 是否必须 描述
    host 必须 要解析的域名
    ip 可选 用户的来源IP,如果没指定这个参数,默认使用请求连接的源IP

    请求示例:

    考虑到服务IP防攻击之类的安全风险,为保障服务可用性,HTTPDNS同时提供多个服务IP,当某个服务IP在异常情况下不可用时,可以使用其它服务IP进行重试。

    请求成功时,HTTP响应的状态码为200,响应结果用JSON格式表示,示例如下:
    {
      "host": "www.suning.com",
      "ips": [
        "112.84.104.48"
      ],
      "ttl": 57,
      "origin_ttl": 120
    }
    
    请求失败的响应示例:
    {
      "code": "MissingArgument"
    }
    

    错误码列表如下:

    错误码 HTTP状态码 描述
    MissingArgument 400 缺少必要参数
    InvalidHost 400 域名格式不合法
    MethodNotAllowed 405 不支持的HTTP方法
    InternalError 500 服务端内部错误
    错误处理

    异常下的出错兼容逻辑,主要包括异步请求,重试,降级

    异步请求

    访问HTTPDNS服务时,应该使用异步请求的策略,避免解析延迟太大而对业务造成影响,特别是在网络环境异常或HTTPDNS服务IP异常不可
    用时,如果用同步访问,需要等待网络超时后才会返回解析失败,这个超时时间较大,可能对业务的使用体验造成很大影响。
    
    异步请求策略:解析域名时,如果当前缓存中有TTL未过期的IP,可直接使用;如果没有,则立刻让此次请求降级走原生LocalDNS解析,同
    时另起线程异步地发起HTTPDNS请求进行解析,更新缓存,这样后续解析域名时就能命中缓存。
    

    重试

    访问HTTPDNS服务解析域名时,如果请求HTTPDNS服务端失败,即HTTP请求没有返回,可以进行重试。
    
    大部分情况下,这种访问失败是由于网络原因引起的,重试可以解决。
    

    降级

    不管是因为什么原因,当通过HTTPDNS服务无法获得域名对应的IP时,都必须降级:使用标准的DNS解析,通过Local DNS去解析域名。
    

    Android端:

    OkHttp默认使用系统DNS服务InetAddress进行域名解析,但同时也暴露了自定义DNS服务的接口,通过该接口我们可以优雅地使用HttpDns。

    • 自定义DNS接口

    OkHttp暴露了一个Dns接口,通过实现该接口,我们可以自定义Dns服务:

    public class OkHttpDns implements Dns {
        private static final Dns SYSTEM = Dns.SYSTEM;
        HttpDnsService httpdns;//httpdns 解析服务
        private static OkHttpDns instance = null;
        private OkHttpDns(Context context) {
            this.httpdns = HttpDns.getService(context, "account id");
        }
        public static OkHttpDns getInstance(Context context) {
            if(instance == null) {
                instance = new OkHttpDns(context);
            }
            return instance;
        }
        @Override
        public List<InetAddress> lookup(String hostname) throws UnknownHostException {
            //通过异步解析接口获取ip
            String ip = httpdns.getIpByHostAsync(hostname);
            if(ip != null) {
                //如果ip不为null,直接使用该ip进行网络请求
                List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));
                Log.e("OkHttpDns", "inetAddresses:" + inetAddresses);
                return inetAddresses;
            }
            //如果返回null,走系统DNS服务解析域名
            return Dns.SYSTEM.lookup(hostname);
        }
    }
    
    • 创建OkHttpClient

    创建OkHttpClient对象,传入OkHttpDns对象代替默认Dns服务:

    private void okhttpDnsRequest() {
        OkHttpClient client = new OkHttpClient.Builder()
        .dns(OkHttpDns.getInstance(getApplicationContext()))
        .build();
        Request request = new Request.Builder()
        .url("http://www.aliyun.com")
        .build();
        Response response = null;
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
                    DataInputStream dis = new DataInputStream(response.body().byteStream());
                    int len;
                    byte[] buff = new byte[4096];
                    StringBuilder result = new StringBuilder();
                    while ((len = dis.read(buff)) != -1) {
                        result.append(new String(buff, 0, len));
                    }
                    Log.d("OkHttpDns", "Response: " + result.toString());
                }
            });
    }
    
    • 总结

    相比于通用方案,OkHttp+HttpDns有以下两个主要优势:

    实现简单,只需通过实现Dns接口即可接入HttpDns服务
    
    通用性强,该方案在HTTPS,SNI以及设置Cookie等场景均适用。规避了证书校验,域名检查等环节
    

    IOS端:

    基于NSURLProtocol可拦截iOS系统上基于上层网络库NSURLConnection/NSURLSession发出的网络请求;

    通过以下接口注册自定义NSURLProtocol,用于拦截上层网络请求,并创建新的网络请求接管数据发送、接收、重定向等处理逻辑,将结果反馈给原始请求。

    [NSURLProtocol registerClass:[CustomProtocol class]];
    

    自定义NSURLProtocol处理过程概述:

    • 在canInitWithRequest中过滤要需要做HTTPDNS域名解析的请求;
    • 请求拦截后,做HTTPDNS域名解析;
    • 解析完成后,同普通请求一样,替换URL.host字段,替换HTTP Header Host域,并接管该请求的数据发送、接收、重定向等处理;

    相关文章

      网友评论

        本文标题:HttpDNS功能说明及实现

        本文链接:https://www.haomeiwen.com/subject/liiqpxtx.html