Android端OkHttp框架获取DNS解析时间和LocalDNS的解析结果指南

本文档介绍了在 Android 上使用 okhttp 库进行网络请求时,获取 DNS 解析时间的方法。

1.概述

在 Android 上使用 okhttp 库进行网络请求时,获取 DNS 解析时间可以通过 EventListener 来实现。EventListener 提供了多个回调方法用于跟踪各个阶段的网络请求,包括 DNS 解析、连接建立、数据传输等。

在 Android 上可以使用InetAddress.getAllByName(String host) 方法获取本地DNS的解析结果,用户可以和期望的解析结果进行比对来判断是否有劫持情况。

2.获取DNS解析时间方案

以下是如何使用 okhttpEventListener 来获取 DNS 解析时间的步骤:

步骤 1:导入okhttp

确保在你的 build.gradle 文件中添加 okhttp 依赖:

implementation 'com.squareup.okhttp3:okhttp:4.9.3'

步骤 2:创建自定义EventListener

你需要创建一个自定义的 EventListener 类,用于监听和记录 DNS 解析时间:

import okhttp3.EventListener;
import okhttp3.Call;
import okhttp3.HttpUrl;

public class DnsTimingEventListener extends EventListener {
    private long dnsStartTime;
    private long dnsEndTime;

    @Override
    public void dnsStart(Call call, String domainName) {
        super.dnsStart(call, domainName);
        dnsStartTime = System.currentTimeMillis();
    }

    @Override
    public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
        super.dnsEnd(call, domainName, inetAddressList);
        dnsEndTime = System.currentTimeMillis();
        long dnsDuration = dnsEndTime - dnsStartTime;
        System.out.println("DNS 解析时间: " + dnsDuration + " 毫秒");
    }
}

步骤 3:创建OkHttpClient实例并设置EventListener.Factory

将自定义的 EventListener 工厂设置到 OkHttpClient 实例中:

import okhttp3.OkHttpClient;

// 创建 OkHttpClient 实例
OkHttpClient client = new OkHttpClient.Builder()
        .eventListenerFactory(new EventListener.Factory() {
            @Override
            public EventListener create(Call call) {
                return new DnsTimingEventListener();
            }
        })
        .build();

步骤 4:执行网络请求

使用配置好的 OkHttpClient 实例执行网络请求:

import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;

public void executeNetworkRequest() {
    // 创建 Request 对象
    Request request = new Request.Builder()
    // .url()中配置实际的 URL 地址
            .url("https://www.example.com")
            .build();

    try {
        // 执行请求
        Response response = client.newCall(request).execute();
        // 处理响应
        if (response.isSuccessful()) {
            System.out.println("请求成功");
            // 可以在此处处理响应数据
        } else {
            System.out.println("请求失败,状态码:" + response.code());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

您可以通过上述方法来获取 DNS 解析时间并进行相应的处理。这不仅可以帮助你了解网络请求的时间消耗情况,还可以用于性能调优和监控。

通过实现OkHttpDNS接口,我们可以使用公共DNS服务。当实现了OkHttpDNS接口且使用了阿里云公共DNS 时,通过上述方法获取的DNS解析时间是阿里云公共DNS解析时间。当没有实现OkHttpDNS接口时,OkHttp默认使用系统DNS服务InetAddress,那么通过上述方法获取的DNS解析时间是系统DNS服务的解析时间。

实现OkHttpDNS接口可以参考以下代码:

  public class OkHttpDns implements Dns {

    private static OkHttpDns instance;
    private static Object lock = new Object();
    private DNSResolver mDNSResolver = DNSResolver.getInstance();

    private OkHttpDns() {
    }

    public static OkHttpDns getInstance() {
        if (null == instance) {
            synchronized (lock) {
                if (instance == null) {
                    instance = new OkHttpDns();
                }
            }
        }
        return instance;
    }

    @Override
    public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException {
        //调用阿里云公共DNS Android SDK提供API进行域名解析
        String ip = null;
        String[] ipv4Array = mDNSResolver.getIpv4ByHostFromCache(hostname,true);
        if (ipv4Array != null && ipv4Array.length > 0) {
            ip = ipv4Array[0];
        }else {
            ip = mDNSResolver.getIPV4ByHost(hostname);
        }

        if (ip != null) {
            //如果ip不为null,直接使用该ip进行网络请求
            Log.d(TAG, "mDnsCache IP: " + ip);
            return Arrays.asList(InetAddress.getAllByName(ip));
        }
        //如果返回null,走系统DNS服务解析域名
        return Dns.SYSTEM.lookup(hostname);
    }
}

3.获取LocalDNS的解析结果并进行劫持检测对比

可以通过以下代码获取LocalDNS解析结果:

public List<String> getIPAddresses(String domain) {
    List<String> ipAddresses = new ArrayList<>();
    try {
        InetAddress[] addresses = InetAddress.getAllByName(domain);
        for (InetAddress address : addresses) {
            ipAddresses.add(address.getHostAddress());
        }
    } catch (UnknownHostException e) {
        e.printStackTrace();
    }
    return ipAddresses;
}

可以参考以下代码实现将获取的LocalDNS解析结果和您期望的解析结果进行比对来判断是否有劫持情况:

public boolean isDnsHijacked(String domain, List<String> expectedIps) {
      List<String> localIps = getIPAddresses(domain);
      if (localIps.isEmpty()) {
          return false; // 无法解析域名
      }
      for (String localIp : localIps) {
          if (!expectedIps.contains(localIp)) {
              return true; // 发现不匹配的 IP,可能存在 DNS 劫持
          }
      }
      return false; // 所有解析结果都匹配
  }