Как получить время разрешения DNS, не используя класс InetAddress или избегая 10-минутного кэшированного времени? - PullRequest
3 голосов
/ 07 апреля 2020

Я пытался получить время разрешения 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

Это работает, как и ожидалось, предоставляя мне разумное время разрешения (100 мс с использованием данных), однако это просто в случае первой попытки, потому что время следующего разрешения слишком мало (0-2 мс с использованием данных). Прочитав документацию, я смог найти причину этого в том, что в случае успеха она кэшируется на 10 минут.

Я попытался вызвать скрытый метод clearDnsCache() класса InerAddress, используя отражение, имеющее чуть более высокое значение. результаты (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))

Я также попробовал решение, которое я прочитал в других вопросах StackOverflow, которое состоит в использовании свойства безопасности Машина JVM. Это не сработало, я думаю, это потому, что для этого потребовалось бы root.

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

Последний вариант, над которым я сейчас работаю, состоит в отправке запроса с использованием класса 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");
        }
    }

Вопрос : знаете ли вы способ разрешения DNS без использования "InetAddress.getByName () "или способ полностью очистить кэш DNS?

Мне нужно : получать реальное (не кэшированное) время разрешения DNS каждый раз, когда я проверяю его, не учитывая, когда я делал последний check.

Я знаю, что в StackOverflow уже есть некоторые вопросы об одной и той же топике c, но большинство из них слишком старые, и ни один из них вообще не мог решить мой вопрос.

1 Ответ

1 голос
/ 04 мая 2020

Я мог бы найти другой способ получить время разрешения DNS и разрешенный IP-адрес, избегая кеширования благодаря коду VisualBasi c из этого сообщения

Решение состоит в отправке DatagramPacket с указанием c запроса к IP-распознавателю DNS через сокет, затем мы ждем ответа, который является временем разрешения, и анализируем ответ, чтобы найти разрешенный IP-адрес.

См. Код:

В новом потоке мы создаем пакет, отправляем его, получаем и декодируем:

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();

            }

    });
}

Метод, который кодирует отправляемый запрос (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;
}

Метод, который декодирует байтовый массив ответа (letDoSomethingWIthThoseBytes):

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());
        }
    }

}

Дополнительная информация:

  • Насколько я знаю, запрос для отправки на сервер может быть изменен в зависимости от того, что вам нужно получить от DNS-сервера. Вы можете узнать больше о протоколе DNS здесь

  • В этом примере я общаюсь с Google DNS (8.8.8.8 / 8.8.4.4), но я проверил еще несколько, и все они с портом 53, поэтому они должны работать. Проверьте какой-нибудь 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/");
  • Большинство ответов, которые я получил от большинства DNS-серверов, содержали разрешенный IP-адрес в конце байтового массива, в частности последние 4 байта, однако, это не относится ко всем из них.

Надеюсь, это решение будет полезно для некоторых.

...