简体   繁体   中英

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:

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). After reading the documentation, I could find the reason for this is because it is cached for 10 mins if successful.

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:

//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. It didn't work, I guess this is because it would require 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).

    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?

I need : Get the real (not cached) DNS resolution time every time I check it without considering when I did the last check.

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.

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

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.

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):

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):

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. You can learn more about the DNS protocol here

  • 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. Check some DNS server:

    ("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.

Hopefully, this solution will be helpful for some.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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