FairEmail/app/src/main/java/eu/faircode/email/DnsHelper.java

712 lines
26 KiB
Java
Raw Normal View History

2020-04-15 18:08:17 +00:00
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
2024-01-01 07:50:49 +00:00
Copyright 2018-2024 by Marcel Bokhorst (M66B)
2020-04-15 18:08:17 +00:00
*/
import android.content.Context;
import android.content.SharedPreferences;
2020-04-15 18:08:17 +00:00
import android.net.ConnectivityManager;
import android.net.DnsResolver;
2020-04-15 18:08:17 +00:00
import android.net.LinkProperties;
import android.net.Network;
import android.os.Build;
2023-02-13 13:20:06 +00:00
import android.text.TextUtils;
2024-01-06 12:15:52 +00:00
import android.util.Base64;
2020-04-15 18:08:17 +00:00
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
2020-04-15 18:08:17 +00:00
import org.minidns.AbstractDnsClient;
2024-01-01 20:18:05 +00:00
import org.minidns.DnsClient;
2024-01-05 12:42:49 +00:00
import org.minidns.dane.DaneVerifier;
2024-01-01 20:18:05 +00:00
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsqueryresult.DnsQueryResult;
import org.minidns.dnsqueryresult.StandardDnsQueryResult;
import org.minidns.dnssec.DnssecClient;
2024-01-02 10:37:37 +00:00
import org.minidns.dnssec.DnssecResultNotAuthenticException;
2024-01-01 20:18:05 +00:00
import org.minidns.dnssec.DnssecValidationFailedException;
import org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism;
import org.minidns.hla.DnssecResolverApi;
2024-01-06 08:00:12 +00:00
import org.minidns.hla.ResolutionUnsuccessfulException;
2024-01-01 20:18:05 +00:00
import org.minidns.hla.ResolverApi;
import org.minidns.hla.ResolverResult;
import org.minidns.record.A;
import org.minidns.record.AAAA;
import org.minidns.record.Data;
import org.minidns.record.MX;
import org.minidns.record.NS;
2024-01-04 08:47:37 +00:00
import org.minidns.record.Record;
2024-01-01 20:18:05 +00:00
import org.minidns.record.SRV;
import org.minidns.record.TXT;
import org.minidns.source.AbstractDnsDataSource;
2024-01-30 19:42:33 +00:00
import org.minidns.source.DnsDataSource;
2024-01-03 17:21:02 +00:00
import org.minidns.util.MultipleIoException;
2020-04-15 18:08:17 +00:00
2024-01-06 12:15:52 +00:00
import java.io.ByteArrayOutputStream;
import java.io.IOException;
2024-01-06 12:52:02 +00:00
import java.io.InputStream;
import java.io.OutputStream;
2024-01-06 12:15:52 +00:00
import java.net.HttpURLConnection;
2020-04-15 18:08:17 +00:00
import java.net.InetAddress;
2024-01-06 13:02:38 +00:00
import java.net.InetSocketAddress;
2024-01-06 12:52:02 +00:00
import java.net.Socket;
2024-01-06 12:15:52 +00:00
import java.net.URL;
2020-04-15 18:08:17 +00:00
import java.net.UnknownHostException;
2024-01-05 12:42:49 +00:00
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
2020-04-16 16:09:55 +00:00
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
2020-04-15 18:08:17 +00:00
import java.util.List;
2023-11-14 06:36:46 +00:00
import java.util.Locale;
2020-09-16 18:57:13 +00:00
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
2024-01-05 12:42:49 +00:00
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
2020-04-15 18:08:17 +00:00
import javax.mail.Address;
import javax.mail.internet.InternetAddress;
2024-01-06 12:15:52 +00:00
import javax.net.ssl.HttpsURLConnection;
2024-01-06 12:52:02 +00:00
import javax.net.ssl.SSLSocketFactory;
2020-04-15 18:08:17 +00:00
2020-04-16 15:39:29 +00:00
public class DnsHelper {
2020-04-15 18:08:17 +00:00
// https://dns.watch/
2024-01-29 19:45:18 +00:00
private static final String DEFAULT_DNS4 = "84.200.69.80";
private static final String DEFAULT_DNS6 = "2001:1608:10:25::1c04:b12f";
2020-07-29 12:08:56 +00:00
private static final int CHECK_TIMEOUT = 5; // seconds
2020-07-21 07:34:56 +00:00
private static final int LOOKUP_TIMEOUT = 15; // seconds
2020-04-15 18:08:17 +00:00
2024-01-07 17:51:29 +00:00
static void init(Context context) {
DnsClient.addDnsServerLookupMechanism(
new AbstractDnsServerLookupMechanism("FairEmail", 1) {
@Override
public boolean isAvailable() {
return true;
}
@Override
public List<String> getDnsServerAddresses() {
List<String> servers = getDnsServers(context);
Log.i("DNS servers=" + TextUtils.join(",", servers));
return servers;
}
});
}
2020-04-16 16:28:12 +00:00
static void checkMx(Context context, Address[] addresses) throws UnknownHostException {
if (addresses == null)
return;
for (Address address : addresses) {
String email = ((InternetAddress) address).getAddress();
2021-07-02 07:51:56 +00:00
String domain = UriHelper.getEmailDomain(email);
if (domain == null)
2020-04-16 16:28:12 +00:00
continue;
2024-01-06 19:18:11 +00:00
DnsRecord[] records = _lookup(context, domain, "mx", CHECK_TIMEOUT);
2024-01-02 18:26:41 +00:00
if (records.length == 0)
throw new UnknownHostException(domain);
2020-04-16 16:28:12 +00:00
}
2020-04-15 18:08:17 +00:00
}
@NonNull
2024-01-01 20:18:05 +00:00
static DnsRecord[] lookup(Context context, String name, String type) {
2024-01-06 19:18:11 +00:00
return _lookup(context, name, type, LOOKUP_TIMEOUT);
2024-01-02 12:58:01 +00:00
}
@NonNull
2024-01-06 19:18:11 +00:00
private static DnsRecord[] _lookup(Context context, String name, String type, int timeout) {
try {
return _lookup(context, name, type, timeout, false);
} catch (Throwable ex) {
if (ex instanceof MultipleIoException ||
2024-01-07 07:19:11 +00:00
ex instanceof ResolutionUnsuccessfulException ||
ex instanceof DnssecValidationFailedException)
2024-01-06 19:18:11 +00:00
Log.i(ex);
else
Log.e(ex);
return new DnsRecord[0];
}
}
@NonNull
private static DnsRecord[] _lookup(Context context, String name, String type, int timeout, boolean dnssec) throws IOException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2024-01-05 19:24:05 +00:00
boolean dns_custom = prefs.getBoolean("dns_custom", false);
2023-11-14 06:36:46 +00:00
String filter = null;
int colon = type.indexOf(':');
if (colon > 0) {
filter = type.substring(colon + 1);
type = type.substring(0, colon);
}
2024-01-01 20:18:05 +00:00
Class<? extends Data> clazz;
2020-04-16 16:09:55 +00:00
switch (type) {
2022-10-12 09:59:26 +00:00
case "ns":
2024-01-01 20:18:05 +00:00
clazz = NS.class;
2022-10-12 09:59:26 +00:00
break;
2020-04-16 16:09:55 +00:00
case "mx":
2024-01-01 20:18:05 +00:00
clazz = MX.class;
break;
2020-04-16 16:09:55 +00:00
case "srv":
2024-01-01 20:18:05 +00:00
clazz = SRV.class;
2020-04-16 16:09:55 +00:00
break;
2021-07-13 08:37:02 +00:00
case "txt":
2024-01-01 20:18:05 +00:00
clazz = TXT.class;
2021-07-13 08:37:02 +00:00
break;
2022-04-25 19:31:20 +00:00
case "a":
2024-01-01 20:18:05 +00:00
clazz = A.class;
2022-04-25 19:31:20 +00:00
break;
case "aaaa":
2024-01-01 20:18:05 +00:00
clazz = AAAA.class;
2022-04-25 19:31:20 +00:00
break;
2020-04-16 16:09:55 +00:00
default:
throw new IllegalArgumentException(type);
}
2020-04-15 18:08:17 +00:00
2024-01-06 19:18:11 +00:00
ResolverApi resolver = DnssecResolverApi.INSTANCE;
AbstractDnsClient client = resolver.getClient();
2024-01-01 20:18:05 +00:00
2024-01-06 19:18:11 +00:00
if (false) {
String private_dns = ConnectionHelper.getPrivateDnsServerName(context);
Log.w("DNS private=" + private_dns);
if (private_dns != null)
client.setDataSource(new DoTDataSource(private_dns));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !dns_custom)
client.setDataSource(new AndroidDataSource());
2024-01-01 20:18:05 +00:00
2024-01-06 19:18:11 +00:00
client.getDataSource().setTimeout(timeout * 1000);
2024-01-31 06:24:09 +00:00
if (!dnssec)
client.setDataSource(new AuthoritiveDataSource(client.getDataSource()));
2024-01-30 19:42:33 +00:00
2024-01-06 19:18:11 +00:00
// https://github.com/MiniDNS/minidns/issues/102
2024-01-31 06:50:46 +00:00
if (client instanceof DnssecClient)
2024-01-06 19:18:11 +00:00
((DnssecClient) client).setUseHardcodedDnsServers(false);
2024-01-04 08:47:37 +00:00
2024-01-31 06:50:46 +00:00
ResolverResult<? extends Data> data;
2024-01-30 06:57:01 +00:00
try {
2024-01-31 06:50:46 +00:00
Log.i("DNS query name=" + type + ":" + name);
data = resolver.resolve(name, clazz);
Log.i("DNS resolved name=" + type + ":" + name +
" success=" + data.wasSuccessful() +
" rcode=" + data.getResponseCode());
2024-01-30 06:57:01 +00:00
data.throwIfErrorResponse();
} catch (Throwable ex) {
2024-01-31 06:50:46 +00:00
Log.w("DNS error message=" + ex.getMessage());
2024-01-30 06:57:01 +00:00
throw ex;
}
2020-04-15 18:08:17 +00:00
2024-01-06 19:18:11 +00:00
boolean secure = (data.getUnverifiedReasons() != null);
2024-01-30 06:57:01 +00:00
Log.i("DNS secure=" + secure + " dnssec=" + dnssec);
2024-01-06 19:18:11 +00:00
if (secure && dnssec) {
DnssecResultNotAuthenticException ex = data.getDnssecResultNotAuthenticException();
if (ex != null)
throw ex;
}
2024-01-06 19:18:11 +00:00
List<DnsRecord> result = new ArrayList<>();
DnsMessage raw = data.getRawAnswer();
List<Record<? extends Data>> answers = (raw == null ? null : raw.answerSection);
Log.i("DNS answers=" + (answers == null ? "n/a" : answers.size()));
if (answers != null) {
Record.TYPE expectedType = data.getQuestion().type;
for (Record<? extends Data> record : answers) {
if (record.type != expectedType) {
Log.i("DNS skip=" + record);
continue;
}
Data answer = record.getPayload();
Log.i("DNS record=" + record + " answer=" + answer);
if (answer instanceof NS) {
NS ns = (NS) answer;
result.add(new DnsRecord(ns.getTarget().toString()));
} else if (answer instanceof MX) {
MX mx = (MX) answer;
result.add(new DnsRecord(mx.target.toString(), 0, mx.priority, 0));
} else if (answer instanceof SRV) {
SRV srv = (SRV) answer;
result.add(new DnsRecord(srv.target.toString(), srv.port, srv.priority, srv.weight));
} else if (answer instanceof TXT) {
StringBuilder sb = new StringBuilder();
TXT txt = (TXT) answer;
for (String text : txt.getCharacterStrings()) {
if (filter != null &&
(TextUtils.isEmpty(text) || !text.toLowerCase(Locale.ROOT).startsWith(filter)))
continue;
int i = 0;
int slash = text.indexOf('\\', i);
while (slash >= 0 && slash + 4 < text.length()) {
String digits = text.substring(slash + 1, slash + 4);
if (TextUtils.isDigitsOnly(digits)) {
int k = Integer.parseInt(digits);
text = text.substring(0, slash) + (char) k + text.substring(slash + 4);
} else
i += 4;
slash = text.indexOf('\\', i);
}
sb.append(text);
}
2024-01-06 19:18:11 +00:00
result.add(new DnsRecord(sb.toString(), 0));
} else if (answer instanceof A) {
A a = (A) answer;
result.add(new DnsRecord(a.getInetAddress()));
} else if (answer instanceof AAAA) {
AAAA aaaa = (AAAA) answer;
result.add(new DnsRecord(aaaa.getInetAddress()));
2024-01-31 06:50:46 +00:00
} else
Log.e("DNS unexpected record=" +
(answer == null ? null : answer.getClass().getName()));
2024-01-06 19:18:11 +00:00
}
2020-04-16 16:09:55 +00:00
}
2024-01-06 19:18:11 +00:00
for (DnsRecord record : result) {
record.query = name;
record.secure = secure;
2024-01-30 06:57:01 +00:00
try {
record.authentic = data.isAuthenticData();
} catch (Throwable ex) {
Log.w(ex);
record.authentic = false;
}
2024-01-06 19:18:11 +00:00
}
if ("mx".equals(type) || "srv".equals(type))
Collections.sort(result, new Comparator<DnsRecord>() {
@Override
public int compare(DnsRecord d1, DnsRecord d2) {
int o = Integer.compare(
d1.priority == null ? 0 : d1.priority,
d2.priority == null ? 0 : d2.priority);
if (o == 0)
o = Integer.compare(
d1.weight == null ? 0 : d1.weight,
d2.weight == null ? 0 : d2.weight);
return o;
}
});
return result.toArray(new DnsRecord[0]);
2020-04-15 18:08:17 +00:00
}
2024-01-05 14:27:03 +00:00
static InetAddress getByName(Context context, String host) throws UnknownHostException {
2024-01-06 14:26:27 +00:00
return getByName(context, host, false);
}
static InetAddress[] getAllByName(Context context, String host) throws UnknownHostException {
return getAllByName(context, host, false);
}
2024-01-06 15:38:22 +00:00
static InetAddress getByName(Context context, String host, boolean dnssec) throws UnknownHostException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean dns_custom = prefs.getBoolean("dns_custom", false);
2024-01-05 17:50:17 +00:00
2024-03-09 19:55:47 +00:00
if (!hasDnsSec())
dnssec = false;
2024-01-06 15:38:22 +00:00
if (!dns_custom && !dnssec)
return InetAddress.getByName(host);
if (ConnectionHelper.isNumericAddress(host))
return InetAddress.getByName(host);
2024-01-05 17:50:17 +00:00
2024-01-06 15:38:22 +00:00
return getAllByName(context, host, dnssec)[0];
2024-01-05 14:27:03 +00:00
}
2024-01-06 15:38:22 +00:00
static InetAddress[] getAllByName(Context context, String host, boolean dnssec) throws UnknownHostException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean dns_custom = prefs.getBoolean("dns_custom", false);
2024-01-05 17:50:17 +00:00
2024-03-09 19:55:47 +00:00
if (!hasDnsSec())
dnssec = false;
2024-01-06 15:38:22 +00:00
if (!dns_custom && !dnssec)
return InetAddress.getAllByName(host);
List<InetAddress> result = new ArrayList<>();
boolean[] has46 = ConnectionHelper.has46(context);
2024-01-06 19:18:11 +00:00
try {
if (has46[0])
for (DnsRecord a : _lookup(context, host, "a", LOOKUP_TIMEOUT, dnssec))
result.add(a.address);
2024-01-05 17:50:17 +00:00
2024-01-06 19:18:11 +00:00
if (has46[1])
for (DnsRecord aaaa : _lookup(context, host, "aaaa", LOOKUP_TIMEOUT, dnssec))
result.add(aaaa.address);
2024-01-06 19:18:11 +00:00
if (result.size() == 0)
throw new UnknownHostException(host);
2024-01-06 19:18:11 +00:00
return result.toArray(new InetAddress[0]);
} catch (IOException ex) {
throw new UnknownHostException(ex.getMessage());
}
2024-01-05 14:27:03 +00:00
}
2024-01-05 12:42:49 +00:00
static void verifyDane(X509Certificate[] chain, String server, int port) throws CertificateException {
2024-03-09 19:55:47 +00:00
if (!hasDnsSec())
return;
2024-01-05 12:42:49 +00:00
Handler handler = new Handler() {
@Override
public void publish(LogRecord record) {
Log.w("DANE " + record.getMessage());
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
}
};
2024-01-25 16:51:30 +00:00
2024-01-05 12:42:49 +00:00
String clazz = DaneVerifier.class.getName();
2024-01-25 16:51:30 +00:00
try {
Logger.getLogger(clazz).addHandler(handler);
Log.w("DANE verify " + server + ":" + port);
boolean verified = new DaneVerifier().verifyCertificateChain(chain, server, port);
Log.w("DANE verified=" + verified + " " + server + ":" + port);
if (!verified)
throw new CertificateException("DANE missing or invalid");
} catch (CertificateException ex) {
throw ex;
} catch (Throwable ex) {
throw new CertificateException("DANE error", ex);
} finally {
Logger.getLogger(clazz).removeHandler(handler);
}
2024-01-05 12:42:49 +00:00
}
2024-01-29 18:54:56 +00:00
static List<String> getDnsServers(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2024-01-31 06:50:46 +00:00
boolean dns_custom = prefs.getBoolean("dns_custom", false);
String dns_extra = prefs.getString("dns_extra", null);
2024-01-31 06:50:46 +00:00
List<String> result = new ArrayList<>();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || dns_custom)
result.addAll(_getDnsServers(context));
2024-01-05 17:50:17 +00:00
if (!TextUtils.isEmpty(dns_extra)) {
String[] extras = dns_extra.replaceAll("\\s+", "").split(",");
for (String extra : extras)
if (ConnectionHelper.isNumericAddress(extra))
result.add(extra);
else
Log.w("DNS extra invalid=" + extra);
}
2024-01-29 19:45:18 +00:00
result.add(DEFAULT_DNS4);
result.add(DEFAULT_DNS6);
return result;
}
private static List<String> _getDnsServers(Context context) {
List<String> result = new ArrayList<>();
2024-01-07 17:51:29 +00:00
try {
ConnectivityManager cm = Helper.getSystemService(context, ConnectivityManager.class);
if (cm == null)
return result;
2024-01-07 17:51:29 +00:00
Network active = ConnectionHelper.getActiveNetwork(context);
if (active == null)
return result;
2024-01-07 17:51:29 +00:00
LinkProperties props = cm.getLinkProperties(active);
if (props == null)
return result;
2024-01-07 17:51:29 +00:00
List<InetAddress> dns = props.getDnsServers();
for (int i = 0; i < dns.size(); i++)
result.add(dns.get(i).getHostAddress());
} catch (Throwable ex) {
Log.e(ex);
}
return result;
}
static void clear(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
for (String key : prefs.getAll().keySet())
if (key != null && key.startsWith("dns."))
editor.remove(key);
editor.apply();
}
2024-03-09 19:55:47 +00:00
static boolean hasDnsSec() {
// DNSSEC causes crashes in libc
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
}
2024-01-01 20:18:05 +00:00
static void test(Context context) throws UnknownHostException {
2024-01-05 12:22:28 +00:00
test(context, "gmail.com", "ns");
2024-01-05 17:50:17 +00:00
test(context, "web.de", "mx");
2024-01-05 12:22:28 +00:00
test(context, "_imaps._tcp.gmail.com", "srv");
test(context, "gmail.com", "txt");
test(context, "outlook.office365.com", "a");
test(context, "outlook.office365.com", "aaaa");
test(context, "posteo.de", "a");
test(context, "non.existent.tld", "a");
test(context, "rubbish", "a");
2024-01-01 20:18:05 +00:00
}
2024-01-05 12:22:28 +00:00
private static void test(Context context, String name, String type) {
DnsRecord[] records = lookup(context, name, type);
Log.w("DNS test " + name + ":" + type);
2024-01-05 17:50:17 +00:00
if (records.length == 0)
Log.w("- no records");
2024-01-01 20:18:05 +00:00
for (DnsRecord record : records)
2024-01-05 12:22:28 +00:00
Log.w("- " + record);
2020-04-15 18:08:17 +00:00
}
2020-04-15 19:25:44 +00:00
2024-01-06 13:15:05 +00:00
private static class DoTDataSource extends AbstractDnsDataSource {
2024-01-06 12:52:02 +00:00
private String host;
private DoTDataSource() {
super();
}
DoTDataSource(String host) {
super();
this.host = host;
}
@Override
public DnsQueryResult query(DnsMessage query, InetAddress address, int port) throws IOException {
// https://datatracker.ietf.org/doc/html/rfc7858
2024-01-06 13:02:38 +00:00
try (Socket socket = SSLSocketFactory.getDefault().createSocket()) {
socket.connect(new InetSocketAddress(host, 853), timeout);
socket.setSoTimeout(timeout);
2024-01-06 12:52:02 +00:00
byte[] out = query.toArray();
OutputStream os = socket.getOutputStream();
2024-01-06 21:06:49 +00:00
os.write(new byte[]{(byte) (out.length / 256), (byte) (out.length % 256)});
2024-01-06 12:52:02 +00:00
os.write(out);
InputStream is = socket.getInputStream();
int hi = is.read();
2024-01-06 13:08:37 +00:00
if (hi < 0)
throw new IOException("EOF");
2024-01-06 12:52:02 +00:00
int lo = is.read();
2024-01-06 13:08:37 +00:00
if (lo < 0)
2024-01-06 12:52:02 +00:00
throw new IOException("EOF");
2024-01-06 13:08:37 +00:00
2024-01-06 12:52:02 +00:00
int len = hi * 256 + lo;
byte[] in = new byte[len];
int i = 0;
while (i < len) {
int r = is.read(in, i, len - i);
if (r < 0)
throw new IOException("EOF");
i += r;
}
return new StandardDnsQueryResult(
address, port,
DnsQueryResult.QueryMethod.tcp,
query,
2024-01-06 13:11:48 +00:00
new DnsMessage(in));
2024-01-06 12:52:02 +00:00
}
}
}
2024-01-06 13:15:05 +00:00
private static class DoHDataSource extends AbstractDnsDataSource {
2024-01-06 12:15:52 +00:00
private String host;
private DoHDataSource() {
super();
}
DoHDataSource(String host) {
super();
this.host = host;
}
@Override
public DnsQueryResult query(DnsMessage query, InetAddress address, int port) throws IOException {
2024-01-06 12:52:02 +00:00
// https://datatracker.ietf.org/doc/html/rfc8484
2024-01-06 12:15:52 +00:00
HttpsURLConnection request = null;
try {
URL url = new URL("https://" + host + "/dns-query?dns=" +
Base64.encodeToString(query.toArray(), Base64.NO_PADDING | Base64.NO_WRAP));
request = (HttpsURLConnection) url.openConnection();
request.setRequestMethod("GET");
request.setRequestProperty("Content-Type", "application/dns-message");
2024-01-06 13:02:38 +00:00
request.setReadTimeout(timeout);
request.setConnectTimeout(timeout);
2024-01-06 12:15:52 +00:00
request.setDoInput(true);
request.connect();
int status = request.getResponseCode();
if (status != HttpURLConnection.HTTP_OK)
throw new IOException("Error " + status + ": " + request.getResponseMessage());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.writeTo(request.getOutputStream());
return new StandardDnsQueryResult(
address, port,
2024-01-06 12:52:02 +00:00
DnsQueryResult.QueryMethod.tcp,
2024-01-06 12:15:52 +00:00
query,
2024-01-06 13:11:48 +00:00
new DnsMessage(bos.toByteArray()));
2024-01-06 12:15:52 +00:00
} finally {
if (request != null)
request.disconnect();
}
}
}
2024-01-06 13:15:05 +00:00
private static class AndroidDataSource extends AbstractDnsDataSource {
2024-01-06 12:14:23 +00:00
private IOException ex;
private DnsQueryResult result;
@Override
public DnsQueryResult query(DnsMessage query, InetAddress address, int port) throws IOException {
Semaphore sem = new Semaphore(0);
DnsResolver resolver = DnsResolver.getInstance();
Log.i("DNS Android query=" + query);
resolver.rawQuery(
null,
query.toArray(),
DnsResolver.FLAG_EMPTY,
new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
},
null,
new DnsResolver.Callback<byte[]>() {
@Override
public void onAnswer(@NonNull byte[] bytes, int rcode) {
try {
2024-01-30 06:57:01 +00:00
Log.i("DNS rcode=" + rcode);
2024-01-06 12:14:23 +00:00
DnsMessage answer = new DnsMessage(bytes)
.asBuilder()
.setResponseCode(DnsMessage.RESPONSE_CODE.getResponseCode(rcode))
.build();
result = new StandardDnsQueryResult(
address, port,
DnsQueryResult.QueryMethod.udp,
query,
answer);
} catch (Throwable e) {
ex = new IOException(e.getMessage(), e);
} finally {
sem.release();
}
}
@Override
public void onError(@NonNull DnsResolver.DnsException e) {
try {
ex = new IOException(e.getMessage(), e);
} finally {
sem.release();
}
}
});
try {
2024-01-06 13:02:38 +00:00
if (!sem.tryAcquire(timeout, TimeUnit.MILLISECONDS))
2024-01-06 12:14:23 +00:00
ex = new IOException("timeout");
} catch (InterruptedException e) {
ex = new IOException("interrupted");
}
if (ex == null) {
Log.i("DNS Android answer=" + result);
return result;
} else {
Log.i(ex);
throw ex;
}
}
}
2024-01-30 19:42:33 +00:00
private static class AuthoritiveDataSource extends AbstractDnsDataSource {
private final DnsDataSource delegate;
AuthoritiveDataSource(DnsDataSource delegate) {
this.delegate = delegate;
}
@Override
public DnsQueryResult query(DnsMessage message, InetAddress address, int port) throws IOException {
DnsQueryResult result = delegate.query(message, address, port);
DnsMessage answer = new DnsMessage(result.response.toArray())
.asBuilder()
.setRecursionAvailable(true)
.build();
return new StandardDnsQueryResult(address, port, result.queryMethod, result.query, answer);
}
}
2020-04-16 16:09:55 +00:00
static class DnsRecord {
String query;
2023-04-05 05:31:14 +00:00
String response;
2020-04-16 16:09:55 +00:00
Integer port;
Integer priority;
Integer weight;
2024-01-01 20:18:05 +00:00
Boolean secure;
2024-01-02 10:37:37 +00:00
Boolean authentic;
2024-01-01 21:54:46 +00:00
InetAddress address;
2020-04-16 16:09:55 +00:00
DnsRecord(String response) {
2023-04-05 05:31:14 +00:00
this.response = response;
2020-04-16 16:09:55 +00:00
}
2024-01-01 21:54:46 +00:00
DnsRecord(InetAddress address) {
this.address = address;
this.response = address.getHostAddress();
}
DnsRecord(String response, int port) {
2023-04-05 05:31:14 +00:00
this.response = response;
2020-04-16 16:09:55 +00:00
this.port = port;
}
DnsRecord(String response, int port, int priority, int weight) {
2023-04-05 05:31:14 +00:00
this.response = response;
this.port = port;
this.priority = priority;
this.weight = weight;
}
@NonNull
@Override
public String toString() {
2024-01-02 10:37:37 +00:00
return query + "=" + response + ":" + port + " " + priority + "/" + weight +
" secure=" + secure + " authentic=" + authentic;
}
2020-04-16 16:09:55 +00:00
}
2020-04-15 18:08:17 +00:00
}