英文:
How to get the DNS resolution time without using the class InetAddress or avoiding the 10 min cached time?
问题
以下是翻译好的内容:
我一直在尝试使用以下代码来获取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
这段代码的运行结果是我预期的,能够为我提供合理的解析时间(使用Data时约为100毫秒)。然而,这只是第一次尝试的情况,因为接下来的解析时间都非常低(使用Data时为0-2毫秒)。在阅读了文档之后,我找到了这种情况的原因,因为如果解析成功,DNS会被缓存10分钟。
我试图使用反射调用`InetAddress`类的隐藏方法`clearDnsCache()`,结果稍微有所改善(使用Data时为2-4毫秒),所以缓存似乎没有完全清除:
// 列表中位置0的方法是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毫秒,后续尝试使用Data时为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;
}
@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");
}
}
问题:您是否知道一种在不使用InetAddress.getByName()
的情况下解析DNS的方法,或者有没有一种完全清除DNS缓存的方法?
我需要:每次检查时,都能够获取实际(未缓存)的DNS解析时间,而不考虑上一次检查的时间。
我知道StackOverflow上已经有一些关于同样主题的问题,但其中大多数都太旧了,而且没有一个能够完全解决我的问题。
英文:
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.
答案1
得分: 2
以下是翻译好的代码部分:
在一个新的线程中创建、发送、接收和解码数据包:
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;
}
解码答案的字节数组方法(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());
}
}
}
希望这些翻译对您有所帮助。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论