简体   繁体   English

如何在不使用 InetAddress 类或避免 10 分钟缓存时间的情况下获取 DNS 解析时间?

[英]How to get the DNS resolution time without using the class InetAddress or avoiding the 10 min cached time?

I've been trying to get the DNS resolution time using the next code:我一直在尝试使用下一个代码获取 DNS 解析时间:

val url = URL(dataExperienceTestResult.pingUrl)
val host: String = url.host

val currentTime: Long = SystemClock.elapsedRealtime()
val address: InetAddress = InetAddress.getByName(host)
val dnsTime: Long = SystemClock.elapsedRealtime() - currentTime

Which works as expected providing me with a reasonable resolution time (100ms using Data), however, this is just the case of the first try because the next resolution times are too low (0-2ms using Data).这按预期工作,为我提供了合理的解析时间(使用数据为 100 毫秒),但是,这只是第一次尝试的情况,因为下一个解析时间太短(使用数据为 0-2 毫秒)。 After reading the documentation, I could find the reason for this is because it is cached for 10 mins if successful.阅读文档后,我可以找到原因是因为如果成功,它会缓存 10 分钟。

I tried to call the hidden method clearDnsCache() of the class InerAddress using reflection having slightly higher results (2-4ms using Data) so the cache doesn't seem to be cleared completely:我尝试使用反射调用类InerAddress的隐藏方法clearDnsCache()具有稍高的结果(使用数据为 2-4 毫秒),因此缓存似乎没有被完全清除:

//The position 0 method of the list is clearDnsCache()
val method = Class.forName(InetAddress::class.java.name).methods[0]
method.invoke(Class.forName(InetAddress::class.java.name))

I also tried a solution that I read in other StackOverflow questions which consist of eating a security property of the JVM machine.我还尝试了我在其他 StackOverflow 问题中读到的解决方案,其中包括使用 JVM 机器的安全属性。 It didn't work, I guess this is because it would require root.它没有用,我想这是因为它需要 root。

Security.setProperty("networkaddress.cache.ttl", "0")

The last option that I'm currently working on consist of sending a query using the DnsResolver class but I'm getting to high results (300ms - first true, 200ms next tries both using Data).我目前正在处理的最后一个选项包括使用 DnsResolver 类发送查询,但我得到了很高的结果(300 毫秒 - 第一个为真,接下来 200 毫秒尝试使用数据)。

    private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    long currentTime;

    @RequiresApi(api = Build.VERSION_CODES.Q)
    public void method(Context context){
        URL url;
        Executor executor = new Handler(Looper.getMainLooper())::post;

        try {
            url = new URL("https://ee-uk.metricelltestcloud.com/SpeedTest/latency.txt");
//
            String host = url.getHost();
            final String msg = "RawQuery " + host;

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
                ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE);
                if (connectivityManager != null) {

                    Network[] networks = connectivityManager.getAllNetworks();
                    currentTime = SystemClock.elapsedRealtime();
                    for (Network network : networks){
                        final VerifyCancelCallback callback = new VerifyCancelCallback(msg);

                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                            DnsResolver resolver = DnsResolver.getInstance();
                            resolver.rawQuery(network, host, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
                        }
                    }
                }
            }


        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
    private static String byteArrayToHexString(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int i = 0; i < bytes.length; ++i) {
            int b = bytes[i] & 0xFF;
            hexChars[i * 2] = HEX_CHARS[b >>> 4];
            hexChars[i * 2 + 1] = HEX_CHARS[b & 0x0F];
        }
        return new String(hexChars);
    }

    @RequiresApi(api = Build.VERSION_CODES.Q)
    class VerifyCancelCallback implements DnsResolver.Callback<byte[]> {
        private String mMsg;
        VerifyCancelCallback(@NonNull String msg) {
            this.mMsg = msg;
//            this(msg, null);
        }
        @Override
        public void onAnswer(@NonNull byte[] answer, int rcode) {
            long dnsTime = SystemClock.elapsedRealtime() - currentTime;
            Log.v("Kanto_Resolver", "Answer " + dnsTime + " ms");
            Log.v("Kanto_resolver", answer.toString());
            Log.d("Kanto_resolver", "Reported rcode: " + rcode);
            Log.d("Kanto_resolver", "Reported blob: " + byteArrayToHexString(answer));
        }
        @Override
        public void onError(@NonNull DnsResolver.DnsException error) {
            Log.v("Kanto_Resolver", "Error");
        }
    }

Question : Do you know a way to resolve the DNS without using "InetAddress.getByName()" or a way to clear completely the DNS cache?问题:您是否知道一种无需使用“InetAddress.getByName()”即可解析 DNS 的方法或一种完全清除 DNS 缓存的方法?

I need : Get the real (not cached) DNS resolution time every time I check it without considering when I did the last check.我需要:每次检查时获取真实的(未缓存的)DNS 解析时间,而不考虑上次检查的时间。

I'm aware that there are already some questions about same topic in StackOverflow but most of them are too old and none of them could solve my question at all.我知道 StackOverflow 中已经有一些关于同一主题的问题,但它们中的大多数都太旧了,根本无法解决我的问题。

I could find another way to get the DNS resolution time and the resolved IP address avoiding caching thanks to the VisualBasic code from this post由于这篇文章中的 VisualBasic 代码,我可以找到另一种方法来获取 DNS 解析时间和已解析的 IP 地址,从而避免缓存

The solution consists of sending a DatagramPacket with a specific query to the DNS IP resolver through the socket, then we wait for the answer, which is the resolution time and we analyze the answer to find the resolved IP.该解决方案包括通过套接字向 DNS IP 解析器发送带有特定查询的 DatagramPacket,然后我们等待答案,即解析时间,我们分析答案以找到已解析的 IP。

See the code:看代码:

In a new thread we create the packet, we send it, we receive it and we decode it:在一个新线程中,我们创建数据包,发送它,接收它并解码它:

public void getDnsStuff() {
    backgroundHandler.post(new Runnable() {
        @Override
        public void run() {

            byte [] lololo;
            try {
                DatagramPacket sendPacket;
                String string = "linkedin.com";
                lololo = giveMeX3(urlToUse);
                sendPacket = new DatagramPacket(lololo, lololo.length, InetAddress.getByName("8.8.8.8"), 53);
                Log.e("kanto_extra", "host: " + string + ", DNS: GoogleDNS");

                socket = new DatagramSocket();
                socket.send(sendPacket);

                byte[] buf = new byte[512];
                DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);

                Long currentTime = SystemClock.elapsedRealtime();
                socket.setSoTimeout(1000);
                socket.receive(receivePacket);
                Long now = SystemClock.elapsedRealtime() - currentTime;
                Log.v("Kanto_time", now.toString());

                int[] bufUnsigned = new int[receivePacket.getLength()];
                for (int x = 0; x < receivePacket.getLength(); x++){
                    bufUnsigned[x] = (int) receivePacket.getData()[x] & 0xFF;
                }
                Log.v("Kanto_unsigned", bufUnsigned.toString());

                letsDoSomethingWIthThoseBytes(bufUnsigned, receivePacket.getData(), lololo, now);

            } catch (IOException e) {
                e.printStackTrace();
            }

            socket.close();
            socket.disconnect();

            }

    });
}

The method that encodes the query to be sent (giveMeX3):对要发送的查询进行编码的方法(giveMeX3):

private byte[] giveMeX3(String host){
    String TransactionID1="Q1";
    String TypeString="\u0001"+"\u0000"+"\u0000"+"\u0001"+"\u0000"+"\u0000"+"\u0000"+"\u0000"+"\u0000"+"\u0000";
    String TrailerString="\u0000"+"\u0000"+"\u0001"+"\u0000"+"\u0001";
    String URLNameStart = host.substring(0, host.indexOf("."));
    String DomainName = host.substring(host.indexOf(".") + 1);
    String QueryString = TransactionID1 + TypeString + (char)URLNameStart.length() + URLNameStart + (char)DomainName.length() + DomainName + TrailerString;

    byte[] buffer = new byte[0];
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        buffer = QueryString.getBytes(StandardCharsets.US_ASCII);
    }
    return buffer;
}

The method that decodes the byte array of the answer (letsDoSomethingWIthThoseBytes):解码答案的字节数组的方法(letsDoSomethingWIthThoseBytes):

public void letsDoSomethingWIthThoseBytes(int[] bytesList, byte[] bytesListTrue, byte[] sentBytes, Long time){
    int index = 0;
    if (bytesList[0] == sentBytes[0] && (bytesList[1] == 0x31) || (bytesList[1] == 0x32)) {
        if (bytesList[2] == 0x81 && bytesList[3] == 0x80) {

            // Decode the answers
            // Find the URL that was returned
            int TransactionDNS = bytesList[1];
            String ReceiveString = "";// = Encoding.ASCII.GetString(Receivebytes);
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
                ReceiveString = new String(bytesListTrue, StandardCharsets.US_ASCII);
            }
            index=12;
            int URLNameStartLength = bytesListTrue[index];
            index++;
            String URLNameStart = ReceiveString.substring(index,URLNameStartLength + index);
            index=index+URLNameStartLength;
            int DomainNameLength = bytesListTrue[index];
            index++;
            String DomainName = ReceiveString.substring(index,DomainNameLength + index);
            index=index+DomainNameLength;
            index=index+8;

            // Get the record type
            int ResponseType = bytesListTrue[index];
            index=index+9;

            int listLenght = bytesList.length;
            String IPResponse = String.valueOf(bytesList[index])+"."
                    + String.valueOf(bytesList[index + 1])+"."
                    + String.valueOf(bytesList[index + 2])+"."
                    + String.valueOf(bytesList[index + 3]);

            this.resultString = URLNameStart + "." + DomainName + " - " + IPResponse + " - " + time.toString();
            Log.v("Kanto DNS answer", URLNameStart + "." + DomainName + " - " + IPResponse + " - " + time.toString());
        }
    }

}

Additional information:附加信息:

  • As far as I know, the query to be sent to the server can be modified depending on what you need to get from the DNS server.据我所知,可以根据您需要从 DNS 服务器获取什么来修改要发送到服务器的查询。 You can learn more about the DNS protocol here您可以在此处了解有关 DNS 协议的更多信息

  • In this example, I'm communicating with Google DNS (8.8.8.8 / 8.8.4.4) but I tested quite a few more and all of them with port 53 so they should work.在这个例子中,我正在与 Google DNS (8.8.8.8 / 8.8.4.4) 通信,但我测试了很多,并且所有这些都使用端口 53,所以它们应该可以工作。 Check some DNS server:检查一些DNS服务器:

    ("Google", "8.8.8.8", "8.8.4.4", "https://developers.google.com/speed/public-dns");
    ("Quad9", "9.9.9.9", "149.112.112.112", "https://www.quad9.net/");
    ("Level 3", "209.244.0.3", "209.244.0.4", "https://www.centurylink.com/business.html?rid=lvltmigration");
    ("Yandex", "77.88.8.8", "77.88.8.1", "https://dns.yandex.com/");
    ("DNSWatch", "84.200.69.80", "84.200.70.40", "https://dns.watch/index");
    ("Verisign", "64.6.64.6", "64.6.65.6", "https://www.verisign.com/en_GB/security-services/public-dns/index.xhtml");
    ("OpenDNS", "208.67.222.222", "208.67.220.220", "https://www.opendns.com/");
    ("FreeDNS", "37.235.1.174", "37.235.1.177", "https://freedns.zone");
    ("Cloudflare", "1.1.1.1", "1.0.0.1", "https://1.1.1.1");
    ("AdGuard", "176.103.130.130", "176.103.130.131", "https://adguard.com/en/adguard-dns/overview.html#instruction");
    ("French Data Network", "80.67.169.12", "80.67.169.40", "https://www.fdn.fr/actions/dns/");
    ("Comodo", "8.26.56.26", "8.20.247.20", "https://www.comodo.com/secure-dns/");
    ("Alternate DNS", "23.253.163.53", "198.101.242.72", "https://alternate-dns.com/");
    ("Freenom World", "80.80.80.80", "80.80.81.81", "https://www.freenom.com");
    ("Keweon", "176.9.62.58", "176.9.62.62", "https://forum.xda-developers.com/android/software-hacking/keweon-privacy-online-security-t3681139");
    ("Quad101", "101.101.101.101", "101.102.103.104", "https://101.101.101.101/index_en.html");
    ("SafeDNS", "195.46.39.39", "195.46.39.40", "https://www.safedns.com/en/");
    ("UncensoredDNS", "91.239.100.100", "89.233.43.71", "https://blog.uncensoreddns.org/");
  • Most of the answers that I received from most of the DNS servers contained the resolved IP address at the end of the byte array, specifically the last 4 bytes, however, this wasn't the case of all of them.我从大多数 DNS 服务器收到的大多数答案都包含在字节数组末尾的已解析 IP 地址,特别是最后 4 个字节,但是,并非所有这些都是这种情况。

Hopefully, this solution will be helpful for some.希望此解决方案对某些人有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM