Detailed changes
@@ -59,7 +59,8 @@ dependencies {
//zxing stopped supporting Java 7 so we have to stick with 3.3.3
//https://github.com/zxing/zxing/issues/1170
implementation 'com.google.zxing:core:3.3.3'
- implementation 'de.measite.minidns:minidns-hla:0.2.4'
+ implementation 'org.minidns:minidns-client:1.0.4'
+ implementation 'org.minidns:minidns-dnssec:1.0.4'
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
implementation 'org.whispersystems:signal-protocol-java:2.6.2'
implementation 'com.makeramen:roundedimageview:2.3.0'
@@ -14,10 +14,15 @@ import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
-import de.measite.minidns.AbstractDNSClient;
-import de.measite.minidns.DNSMessage;
-import de.measite.minidns.Record;
-import de.measite.minidns.record.Data;
+import org.minidns.AbstractDnsClient;
+import org.minidns.dnsmessage.DnsMessage;
+import org.minidns.dnsqueryresult.DirectCachedDnsQueryResult;
+import org.minidns.dnsqueryresult.DnsQueryResult;
+import org.minidns.dnsqueryresult.StandardDnsQueryResult;
+import org.minidns.dnsqueryresult.SynthesizedCachedDnsQueryResult;
+import org.minidns.record.Data;
+
+import org.minidns.record.Record;
import eu.siacs.conversations.Config;
@@ -27,11 +32,11 @@ import java.time.Duration;
import java.util.Collections;
import java.util.List;
-public class AndroidDNSClient extends AbstractDNSClient {
+public class AndroidDNSClient extends AbstractDnsClient {
private static final long DNS_MAX_TTL = 86_400L;
- private static final LruCache<QuestionServerTuple, DNSMessage> QUERY_CACHE =
+ private static final LruCache<QuestionServerTuple, DnsMessage> QUERY_CACHE =
new LruCache<>(1024);
private final Context context;
private final NetworkDataSource networkDataSource = new NetworkDataSource();
@@ -60,7 +65,7 @@ public class AndroidDNSClient extends AbstractDNSClient {
}
@Override
- protected DNSMessage.Builder newQuestion(final DNSMessage.Builder message) {
+ protected DnsMessage.Builder newQuestion(final DnsMessage.Builder message) {
message.setRecursionDesired(true);
message.getEdnsBuilder()
.setUdpPayloadSize(networkDataSource.getUdpPayloadSize())
@@ -69,15 +74,16 @@ public class AndroidDNSClient extends AbstractDNSClient {
}
@Override
- protected DNSMessage query(final DNSMessage.Builder queryBuilder) throws IOException {
- final DNSMessage question = newQuestion(queryBuilder).build();
+ protected DnsQueryResult query(final DnsMessage.Builder queryBuilder) throws IOException {
+ final DnsMessage question = newQuestion(queryBuilder).build();
for (final DNSServer dnsServer : getDNSServers()) {
final QuestionServerTuple cacheKey = new QuestionServerTuple(dnsServer, question);
- final DNSMessage cachedResponse = queryCache(cacheKey);
+ final DnsMessage cachedResponse = queryCache(cacheKey);
if (cachedResponse != null) {
- return cachedResponse;
+ return new CachedDnsQueryResult(question, cachedResponse);
}
- final DNSMessage response = this.networkDataSource.query(question, dnsServer);
+ final DnsQueryResult result = this.networkDataSource.query(question, dnsServer);
+ final var response = result.response;
if (response == null) {
continue;
}
@@ -89,7 +95,7 @@ public class AndroidDNSClient extends AbstractDNSClient {
continue;
}
cacheQuery(cacheKey, response);
- return response;
+ return new StandardDnsQueryResult(dnsServer.inetAddress, dnsServer.port,result.queryMethod,question,response);
}
return null;
}
@@ -104,8 +110,7 @@ public class AndroidDNSClient extends AbstractDNSClient {
private List<DNSServer> getDNSServers() {
final ImmutableList.Builder<DNSServer> dnsServerBuilder = new ImmutableList.Builder<>();
- final ConnectivityManager connectivityManager =
- (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final ConnectivityManager connectivityManager = context.getSystemService(ConnectivityManager.class);
final Network[] networks = getActiveNetworks(connectivityManager);
for (final Network network : networks) {
final LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
@@ -133,17 +138,15 @@ public class AndroidDNSClient extends AbstractDNSClient {
if (connectivityManager == null) {
return new Network[0];
}
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
- final Network activeNetwork = connectivityManager.getActiveNetwork();
- if (activeNetwork != null) {
+ final Network activeNetwork = connectivityManager.getActiveNetwork();
+ if (activeNetwork != null) {
return new Network[] {activeNetwork};
- }
}
return connectivityManager.getAllNetworks();
}
- private DNSMessage queryCache(final QuestionServerTuple key) {
- final DNSMessage cachedResponse;
+ private DnsMessage queryCache(final QuestionServerTuple key) {
+ final DnsMessage cachedResponse;
synchronized (QUERY_CACHE) {
cachedResponse = QUERY_CACHE.get(key);
if (cachedResponse == null) {
@@ -163,7 +166,7 @@ public class AndroidDNSClient extends AbstractDNSClient {
return cachedResponse;
}
- private void cacheQuery(final QuestionServerTuple key, final DNSMessage response) {
+ private void cacheQuery(final QuestionServerTuple key, final DnsMessage response) {
if (response.receiveTimestamp <= 0) {
return;
}
@@ -172,7 +175,7 @@ public class AndroidDNSClient extends AbstractDNSClient {
}
}
- private static long ttl(final DNSMessage dnsMessage) {
+ private static long ttl(final DnsMessage dnsMessage) {
final List<Record<? extends Data>> answerSection = dnsMessage.answerSection;
if (answerSection == null || answerSection.isEmpty()) {
final List<Record<? extends Data>> authoritySection = dnsMessage.authoritySection;
@@ -187,19 +190,19 @@ public class AndroidDNSClient extends AbstractDNSClient {
}
}
- private static long expiresAt(final DNSMessage dnsMessage) {
+ private static long expiresAt(final DnsMessage dnsMessage) {
return dnsMessage.receiveTimestamp + (Math.min(DNS_MAX_TTL, ttl(dnsMessage)) * 1000L);
}
- private static long expiresIn(final DNSMessage dnsMessage) {
+ private static long expiresIn(final DnsMessage dnsMessage) {
return expiresAt(dnsMessage) - System.currentTimeMillis();
}
private static class QuestionServerTuple {
private final DNSServer dnsServer;
- private final DNSMessage question;
+ private final DnsMessage question;
- private QuestionServerTuple(final DNSServer dnsServer, final DNSMessage question) {
+ private QuestionServerTuple(final DNSServer dnsServer, final DnsMessage question) {
this.dnsServer = dnsServer;
this.question = question.asNormalizedVersion();
}
@@ -218,4 +221,11 @@ public class AndroidDNSClient extends AbstractDNSClient {
return Objects.hashCode(dnsServer, question);
}
}
+
+ public static class CachedDnsQueryResult extends DnsQueryResult {
+
+ private CachedDnsQueryResult(final DnsMessage query, final DnsMessage response) {
+ super(QueryMethod.cachedDirect, query, response);
+ }
+ }
}
@@ -7,11 +7,11 @@ import com.google.common.base.Strings;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
-import de.measite.minidns.DNSMessage;
import eu.siacs.conversations.Config;
import org.conscrypt.OkHostnameVerifier;
+import org.minidns.dnsmessage.DnsMessage;
import java.io.Closeable;
import java.io.DataInputStream;
@@ -41,7 +41,7 @@ final class DNSSocket implements Closeable {
public static final int QUERY_TIMEOUT = 5_000;
private final Semaphore semaphore = new Semaphore(1);
- private final Map<Integer, SettableFuture<DNSMessage>> inFlightQueries = new HashMap<>();
+ private final Map<Integer, SettableFuture<DnsMessage>> inFlightQueries = new HashMap<>();
private final Socket socket;
private final DataInputStream dataInputStream;
private final DataOutputStream dataOutputStream;
@@ -59,8 +59,8 @@ final class DNSSocket implements Closeable {
private void readDNSMessages() {
try {
while (socket.isConnected()) {
- final DNSMessage response = readDNSMessage();
- final SettableFuture<DNSMessage> future;
+ final DnsMessage response = readDNSMessage();
+ final SettableFuture<DnsMessage> future;
synchronized (inFlightQueries) {
future = inFlightQueries.remove(response.id);
}
@@ -78,10 +78,10 @@ final class DNSSocket implements Closeable {
private void evictInFlightQueries(final Exception e) {
synchronized (inFlightQueries) {
- final Iterator<Map.Entry<Integer, SettableFuture<DNSMessage>>> iterator =
+ final Iterator<Map.Entry<Integer, SettableFuture<DnsMessage>>> iterator =
inFlightQueries.entrySet().iterator();
while (iterator.hasNext()) {
- final Map.Entry<Integer, SettableFuture<DNSMessage>> entry = iterator.next();
+ final Map.Entry<Integer, SettableFuture<DnsMessage>> entry = iterator.next();
entry.getValue().setException(e);
iterator.remove();
}
@@ -95,14 +95,11 @@ final class DNSSocket implements Closeable {
}
public static DNSSocket connect(final DNSServer dnsServer) throws IOException {
- switch (dnsServer.uniqueTransport()) {
- case TCP:
- return connectTcpSocket(dnsServer);
- case TLS:
- return connectTlsSocket(dnsServer);
- default:
- throw new IllegalStateException("This is not a socket based transport");
- }
+ return switch (dnsServer.uniqueTransport()) {
+ case TCP -> connectTcpSocket(dnsServer);
+ case TLS -> connectTlsSocket(dnsServer);
+ default -> throw new IllegalStateException("This is not a socket based transport");
+ };
}
private static DNSSocket connectTcpSocket(final DNSServer dnsServer) throws IOException {
@@ -133,10 +130,9 @@ final class DNSSocket implements Closeable {
sslSocket.startHandshake();
final SSLSession session = sslSocket.getSession();
final Certificate[] peerCertificates = session.getPeerCertificates();
- if (peerCertificates.length == 0 || !(peerCertificates[0] instanceof X509Certificate)) {
+ if (peerCertificates.length == 0 || !(peerCertificates[0] instanceof X509Certificate certificate)) {
throw new IOException("Peer did not provide X509 certificates");
}
- final X509Certificate certificate = (X509Certificate) peerCertificates[0];
if (!OkHostnameVerifier.strictInstance().verify(dnsServer.hostname, certificate)) {
throw new SSLPeerUnverifiedException("Peer did not provide valid certificates");
}
@@ -144,7 +140,7 @@ final class DNSSocket implements Closeable {
return DNSSocket.of(sslSocket);
}
- public DNSMessage query(final DNSMessage query) throws IOException, InterruptedException {
+ public DnsMessage query(final DnsMessage query) throws IOException, InterruptedException {
try {
return queryAsync(query).get(QUERY_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (final ExecutionException e) {
@@ -159,9 +155,9 @@ final class DNSSocket implements Closeable {
}
}
- public ListenableFuture<DNSMessage> queryAsync(final DNSMessage query)
+ public ListenableFuture<DnsMessage> queryAsync(final DnsMessage query)
throws InterruptedException, IOException {
- final SettableFuture<DNSMessage> responseFuture = SettableFuture.create();
+ final SettableFuture<DnsMessage> responseFuture = SettableFuture.create();
synchronized (this.inFlightQueries) {
this.inFlightQueries.put(query.id, responseFuture);
}
@@ -175,7 +171,7 @@ final class DNSSocket implements Closeable {
return responseFuture;
}
- private DNSMessage readDNSMessage() throws IOException {
+ private DnsMessage readDNSMessage() throws IOException {
final int length = this.dataInputStream.readUnsignedShort();
byte[] data = new byte[length];
int read = 0;
@@ -11,13 +11,14 @@ import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.collect.ImmutableList;
-import de.measite.minidns.DNSMessage;
-import de.measite.minidns.MiniDNSException;
-import de.measite.minidns.source.DNSDataSource;
-import de.measite.minidns.util.MultipleIoException;
-
import eu.siacs.conversations.Config;
+import org.minidns.MiniDnsException;
+import org.minidns.dnsmessage.DnsMessage;
+import org.minidns.dnsqueryresult.DnsQueryResult;
+import org.minidns.dnsqueryresult.StandardDnsQueryResult;
+import org.minidns.util.MultipleIoException;
+
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
@@ -28,7 +29,7 @@ import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-public class NetworkDataSource extends DNSDataSource {
+public class NetworkDataSource extends org.minidns.source.NetworkDataSource {
private static final LoadingCache<DNSServer, DNSSocket> socketCache =
CacheBuilder.newBuilder()
@@ -66,7 +67,8 @@ public class NetworkDataSource extends DNSDataSource {
}
@Override
- public DNSMessage query(final DNSMessage message, final InetAddress address, final int port)
+ public StandardDnsQueryResult query(
+ final DnsMessage message, final InetAddress address, final int port)
throws IOException {
final List<Transport> transports = transportsForPort(port);
Log.w(
@@ -80,16 +82,21 @@ public class NetworkDataSource extends DNSDataSource {
return query(message, new DNSServer(address, port, transports));
}
- public DNSMessage query(final DNSMessage message, final DNSServer dnsServer)
+ public StandardDnsQueryResult query(final DnsMessage message, final DNSServer dnsServer)
throws IOException {
Log.d(Config.LOGTAG, "using " + dnsServer);
final List<IOException> ioExceptions = new ArrayList<>();
for (final Transport transport : dnsServer.transports) {
try {
- final DNSMessage response =
+ final DnsMessage response =
queryWithUniqueTransport(message, dnsServer.asUniqueTransport(transport));
if (response != null && !response.truncated) {
- return response;
+ return new StandardDnsQueryResult(
+ dnsServer.inetAddress,
+ dnsServer.port,
+ transportToMethod(transport),
+ message,
+ response);
}
} catch (final IOException e) {
ioExceptions.add(e);
@@ -101,23 +108,26 @@ public class NetworkDataSource extends DNSDataSource {
return null;
}
- private DNSMessage queryWithUniqueTransport(final DNSMessage message, final DNSServer dnsServer)
+ private static DnsQueryResult.QueryMethod transportToMethod(final Transport transport) {
+ return switch (transport) {
+ case UDP -> DnsQueryResult.QueryMethod.udp;
+ default -> DnsQueryResult.QueryMethod.tcp;
+ };
+ }
+
+ private DnsMessage queryWithUniqueTransport(final DnsMessage message, final DNSServer dnsServer)
throws IOException, InterruptedException {
final Transport transport = dnsServer.uniqueTransport();
- switch (transport) {
- case UDP:
- return queryUdp(message, dnsServer.inetAddress, dnsServer.port);
- case TCP:
- case TLS:
- return queryDnsSocket(message, dnsServer);
- default:
- throw new IOException(
- String.format("Transport %s has not been implemented", transport));
- }
+ return switch (transport) {
+ case UDP -> queryUdp(message, dnsServer.inetAddress, dnsServer.port);
+ case TCP, TLS -> queryDnsSocket(message, dnsServer);
+ default -> throw new IOException(
+ String.format("Transport %s has not been implemented", transport));
+ };
}
- protected DNSMessage queryUdp(
- final DNSMessage message, final InetAddress address, final int port)
+ protected DnsMessage queryUdp(
+ final DnsMessage message, final InetAddress address, final int port)
throws IOException {
final DatagramPacket request = message.asDatagram(address, port);
final byte[] buffer = new byte[udpPayloadSize];
@@ -126,15 +136,15 @@ public class NetworkDataSource extends DNSDataSource {
socket.send(request);
final DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
- final DNSMessage dnsMessage = readDNSMessage(response.getData());
+ final DnsMessage dnsMessage = readDNSMessage(response.getData());
if (dnsMessage.id != message.id) {
- throw new MiniDNSException.IdMismatch(message, dnsMessage);
+ throw new MiniDnsException.IdMismatch(message, dnsMessage);
}
return dnsMessage;
}
}
- protected DNSMessage queryDnsSocket(final DNSMessage message, final DNSServer dnsServer)
+ protected DnsMessage queryDnsSocket(final DnsMessage message, final DNSServer dnsServer)
throws IOException, InterruptedException {
final DNSSocket cachedDnsSocket = socketCache.getIfPresent(dnsServer);
if (cachedDnsSocket != null) {
@@ -159,9 +169,9 @@ public class NetworkDataSource extends DNSDataSource {
}
}
- public static DNSMessage readDNSMessage(final byte[] bytes) throws IOException {
+ public static DnsMessage readDNSMessage(final byte[] bytes) throws IOException {
try {
- return new DNSMessage(bytes);
+ return new DnsMessage(bytes);
} catch (final IllegalArgumentException e) {
throw new IOException(Throwables.getRootCause(e));
}
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015-2022 the original author or authors
+ *
+ * This software is licensed under the Apache License, Version 2.0,
+ * the GNU Lesser General Public License version 2 or later ("LGPL")
+ * and the WTFPL.
+ * You may choose either license to govern your use of this software only
+ * upon the condition that you accept all of the terms of either
+ * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
+ */
+package de.gultsch.minidns;
+
+import org.minidns.MiniDnsException;
+import org.minidns.dnsmessage.Question;
+import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
+
+import java.io.Serial;
+
+public class ResolutionUnsuccessfulException extends MiniDnsException {
+
+ /**
+ *
+ */
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public final Question question;
+ public final RESPONSE_CODE responseCode;
+
+ public ResolutionUnsuccessfulException(Question question, RESPONSE_CODE responseCode) {
+ super("Asking for " + question + " yielded an error response " + responseCode);
+ this.question = question;
+ this.responseCode = responseCode;
+ }
+}
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2015-2022 the original author or authors
+ *
+ * This software is licensed under the Apache License, Version 2.0,
+ * the GNU Lesser General Public License version 2 or later ("LGPL")
+ * and the WTFPL.
+ * You may choose either license to govern your use of this software only
+ * upon the condition that you accept all of the terms of either
+ * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
+ */
+package de.gultsch.minidns;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.minidns.MiniDnsException;
+import org.minidns.MiniDnsException.NullResultException;
+import org.minidns.dnsmessage.DnsMessage;
+import org.minidns.dnsmessage.Question;
+import org.minidns.dnsqueryresult.DnsQueryResult;
+import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
+import org.minidns.dnssec.DnssecResultNotAuthenticException;
+import org.minidns.dnssec.DnssecUnverifiedReason;
+import org.minidns.record.Data;
+
+public class ResolverResult<D extends Data> {
+
+ protected final Question question;
+ private final RESPONSE_CODE responseCode;
+ private final Set<D> data;
+ private final boolean isAuthenticData;
+ protected final Set<DnssecUnverifiedReason> unverifiedReasons;
+ protected final DnsMessage answer;
+ protected final DnsQueryResult result;
+
+ public ResolverResult(Question question, DnsQueryResult result, Set<DnssecUnverifiedReason> unverifiedReasons) throws NullResultException {
+ // TODO: Is this null check still needed?
+ if (result == null) {
+ throw new MiniDnsException.NullResultException(question.asMessageBuilder().build());
+ }
+
+ this.result = result;
+
+ DnsMessage answer = result.response;
+ this.question = question;
+ this.responseCode = answer.responseCode;
+ this.answer = answer;
+
+ Set<D> r = answer.getAnswersFor(question);
+ if (r == null) {
+ this.data = Collections.emptySet();
+ } else {
+ this.data = Collections.unmodifiableSet(r);
+ }
+
+ if (unverifiedReasons == null) {
+ this.unverifiedReasons = null;
+ isAuthenticData = false;
+ } else {
+ this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons);
+ isAuthenticData = this.unverifiedReasons.isEmpty();
+ }
+ }
+
+ public boolean wasSuccessful() {
+ return responseCode == RESPONSE_CODE.NO_ERROR;
+ }
+
+ public Set<D> getAnswers() {
+ throwIseIfErrorResponse();
+ return data;
+ }
+
+ public Set<D> getAnswersOrEmptySet() {
+ return data;
+ }
+
+ public RESPONSE_CODE getResponseCode() {
+ return responseCode;
+ }
+
+ public boolean isAuthenticData() {
+ throwIseIfErrorResponse();
+ return isAuthenticData;
+ }
+
+ /**
+ * Get the reasons the result could not be verified if any exists.
+ *
+ * @return The reasons the result could not be verified or <code>null</code>.
+ */
+ public Set<DnssecUnverifiedReason> getUnverifiedReasons() {
+ throwIseIfErrorResponse();
+ return unverifiedReasons;
+ }
+
+ public Question getQuestion() {
+ return question;
+ }
+
+ public void throwIfErrorResponse() throws ResolutionUnsuccessfulException {
+ ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();
+ if (resolutionUnsuccessfulException != null) throw resolutionUnsuccessfulException;
+ }
+
+ private ResolutionUnsuccessfulException resolutionUnsuccessfulException;
+
+ public ResolutionUnsuccessfulException getResolutionUnsuccessfulException() {
+ if (wasSuccessful()) return null;
+
+ if (resolutionUnsuccessfulException == null) {
+ resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode);
+ }
+
+ return resolutionUnsuccessfulException;
+ }
+
+ private DnssecResultNotAuthenticException dnssecResultNotAuthenticException;
+
+ public DnssecResultNotAuthenticException getDnssecResultNotAuthenticException() {
+ if (!wasSuccessful())
+ return null;
+ if (isAuthenticData)
+ return null;
+
+ if (dnssecResultNotAuthenticException == null) {
+ dnssecResultNotAuthenticException = DnssecResultNotAuthenticException.from(getUnverifiedReasons());
+ }
+
+ return dnssecResultNotAuthenticException;
+ }
+
+ /**
+ * Get the raw answer DNS message we received. <b>This is likely not what you want</b>, try {@link #getAnswers()} instead.
+ *
+ * @return the raw answer DNS Message.
+ * @see #getAnswers()
+ */
+ public DnsMessage getRawAnswer() {
+ return answer;
+ }
+
+ public DnsQueryResult getDnsQueryResult() {
+ return result;
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(getClass().getName()).append('\n')
+ .append("Question: ").append(question).append('\n')
+ .append("Response Code: ").append(responseCode).append('\n');
+
+ if (responseCode == RESPONSE_CODE.NO_ERROR) {
+ if (isAuthenticData) {
+ sb.append("Results verified via DNSSEC\n");
+ }
+ if (hasUnverifiedReasons()) {
+ sb.append(unverifiedReasons).append('\n');
+ }
+ sb.append(answer.answerSection);
+ }
+
+ return sb.toString();
+ }
+
+ boolean hasUnverifiedReasons() {
+ return unverifiedReasons != null && !unverifiedReasons.isEmpty();
+ }
+
+ protected void throwIseIfErrorResponse() {
+ ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();
+ if (resolutionUnsuccessfulException != null)
+ throw new IllegalStateException("Can not perform operation because the DNS resolution was unsuccessful",
+ resolutionUnsuccessfulException);
+ }
+}
@@ -1,5 +1,6 @@
package eu.siacs.conversations;
+import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
@@ -14,9 +15,17 @@ import eu.siacs.conversations.utils.ExceptionHelper;
public class Conversations extends Application {
+ @SuppressLint("StaticFieldLeak")
+ private static Context CONTEXT;
+
+ public static Context getContext() {
+ return Conversations.CONTEXT;
+ }
+
@Override
public void onCreate() {
super.onCreate();
+ CONTEXT = this.getApplicationContext();
ExceptionHelper.init(getApplicationContext());
applyThemeSettings();
}
@@ -1267,7 +1267,6 @@ public class XmppConnectionService extends Service {
} catch (Throwable throwable) {
Log.e(Config.LOGTAG, "unable to initialize security provider", throwable);
}
- Resolver.init(this);
updateMemorizingTrustManager();
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
@@ -1,92 +0,0 @@
-/*
- * Copyright 2015-2016 the original author or authors
- *
- * This software is licensed under the Apache License, Version 2.0,
- * the GNU Lesser General Public License version 2 or later ("LGPL")
- * and the WTFPL.
- * You may choose either license to govern your use of this software only
- * upon the condition that you accept all of the terms of either
- * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
- */
-package eu.siacs.conversations.utils;
-
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.LineNumberReader;
-import java.net.InetAddress;
-import java.util.HashSet;
-import java.util.logging.Level;
-
-import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism;
-import de.measite.minidns.dnsserverlookup.AndroidUsingReflection;
-import de.measite.minidns.dnsserverlookup.DNSServerLookupMechanism;
-import de.measite.minidns.util.PlatformDetection;
-
-/**
- * Try to retrieve the list of DNS server by executing getprop.
- */
-public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanism {
-
- public static final DNSServerLookupMechanism INSTANCE = new AndroidUsingExecLowPriority();
- public static final int PRIORITY = AndroidUsingReflection.PRIORITY + 1;
-
- private AndroidUsingExecLowPriority() {
- super(AndroidUsingExecLowPriority.class.getSimpleName(), PRIORITY);
- }
-
- @Override
- public String[] getDnsServerAddresses() {
- try {
- Process process = Runtime.getRuntime().exec("getprop");
- InputStream inputStream = process.getInputStream();
- LineNumberReader lnr = new LineNumberReader(
- new InputStreamReader(inputStream));
- String line;
- HashSet<String> server = new HashSet<>(6);
- while ((line = lnr.readLine()) != null) {
- int split = line.indexOf("]: [");
- if (split == -1) {
- continue;
- }
- String property = line.substring(1, split);
- String value = line.substring(split + 4, line.length() - 1);
-
- if (value.isEmpty()) {
- continue;
- }
-
- if (property.endsWith(".dns") || property.endsWith(".dns1") ||
- property.endsWith(".dns2") || property.endsWith(".dns3") ||
- property.endsWith(".dns4")) {
-
- // normalize the address
-
- InetAddress ip = InetAddress.getByName(value);
-
- if (ip == null) continue;
-
- value = ip.getHostAddress();
-
- if (value == null) continue;
- if (value.length() == 0) continue;
-
- server.add(value);
- }
- }
- if (server.size() > 0) {
- return server.toArray(new String[server.size()]);
- }
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Exception in findDNSByExec", e);
- }
- return null;
- }
-
- @Override
- public boolean isAvailable() {
- return PlatformDetection.isAndroid();
- }
-
-}
@@ -1,90 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkInfo;
-import android.net.RouteInfo;
-import android.os.Build;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.List;
-
-import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism;
-import de.measite.minidns.dnsserverlookup.AndroidUsingExec;
-
-public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism {
-
- private final Context context;
-
- AndroidUsingLinkProperties(Context context) {
- super(AndroidUsingLinkProperties.class.getSimpleName(), AndroidUsingExec.PRIORITY - 1);
- this.context = context;
- }
-
- @Override
- public boolean isAvailable() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
- }
-
- @Override
- @TargetApi(21)
- public String[] getDnsServerAddresses() {
- final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- final Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks();
- if (networks == null) {
- return new String[0];
- }
- final Network activeNetwork = getActiveNetwork(connectivityManager);
- final List<String> servers = new ArrayList<>();
- int vpnOffset = 0;
- for(Network network : networks) {
- LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
- if (linkProperties == null) {
- continue;
- }
- final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
- final boolean isActiveNetwork = network.equals(activeNetwork);
- final boolean isVpn = networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_VPN;
- if (isActiveNetwork && isVpn) {
- final List<String> tmp = getIPv4First(linkProperties.getDnsServers());
- servers.addAll(0, tmp);
- vpnOffset += tmp.size();
- } else if (hasDefaultRoute(linkProperties) || isActiveNetwork || activeNetwork == null || isVpn) {
- servers.addAll(vpnOffset, getIPv4First(linkProperties.getDnsServers()));
- }
- }
- return servers.toArray(new String[0]);
- }
-
- @TargetApi(23)
- private static Network getActiveNetwork(ConnectivityManager cm) {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? cm.getActiveNetwork() : null;
- }
-
- private static List<String> getIPv4First(List<InetAddress> in) {
- List<String> out = new ArrayList<>();
- for(InetAddress address : in) {
- if (address instanceof Inet4Address) {
- out.add(0, address.getHostAddress());
- } else {
- out.add(address.getHostAddress());
- }
- }
- return out;
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private static boolean hasDefaultRoute(LinkProperties linkProperties) {
- for(RouteInfo route: linkProperties.getRoutes()) {
- if (route.isDefaultRoute()) {
- return true;
- }
- }
- return false;
- }
-}
@@ -6,85 +6,83 @@ import android.util.Log;
import androidx.annotation.NonNull;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
import com.google.common.base.Strings;
-import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
import com.google.common.net.InetAddresses;
import com.google.common.primitives.Ints;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import de.gultsch.minidns.AndroidDNSClient;
+import de.gultsch.minidns.ResolverResult;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.Conversations;
+import eu.siacs.conversations.xmpp.Jid;
+
+import org.minidns.dnsmessage.Question;
+import org.minidns.dnsname.DnsName;
+import org.minidns.dnsqueryresult.DnsQueryResult;
+import org.minidns.record.A;
+import org.minidns.record.AAAA;
+import org.minidns.record.CNAME;
+import org.minidns.record.Data;
+import org.minidns.record.InternetAddressRR;
+import org.minidns.record.Record;
+import org.minidns.record.SRV;
-import java.io.IOException;
-import java.lang.reflect.Field;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
-
-import de.gultsch.minidns.AndroidDNSClient;
-import de.measite.minidns.AbstractDNSClient;
-import de.measite.minidns.DNSCache;
-import de.measite.minidns.DNSClient;
-import de.measite.minidns.DNSName;
-import de.measite.minidns.Question;
-import de.measite.minidns.Record;
-import de.measite.minidns.cache.LRUCache;
-import de.measite.minidns.dnssec.DNSSECResultNotAuthenticException;
-import de.measite.minidns.dnsserverlookup.AndroidUsingExec;
-import de.measite.minidns.hla.DnssecResolverApi;
-import de.measite.minidns.hla.ResolverApi;
-import de.measite.minidns.hla.ResolverResult;
-import de.measite.minidns.iterative.ReliableDNSClient;
-import de.measite.minidns.record.A;
-import de.measite.minidns.record.AAAA;
-import de.measite.minidns.record.CNAME;
-import de.measite.minidns.record.Data;
-import de.measite.minidns.record.InternetAddressRR;
-import de.measite.minidns.record.SRV;
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.xmpp.Jid;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
public class Resolver {
+ private static final Comparator<Result> RESULT_COMPARATOR =
+ (left, right) -> {
+ if (left.priority == right.priority) {
+ if (left.directTls == right.directTls) {
+ if (left.ip == null && right.ip == null) {
+ return 0;
+ } else if (left.ip != null && right.ip != null) {
+ if (left.ip instanceof Inet4Address
+ && right.ip instanceof Inet4Address) {
+ return 0;
+ } else {
+ return left.ip instanceof Inet4Address ? -1 : 1;
+ }
+ } else {
+ return left.ip != null ? -1 : 1;
+ }
+ } else {
+ return left.directTls ? -1 : 1;
+ }
+ } else {
+ return left.priority - right.priority;
+ }
+ };
+
+ private static final ExecutorService DNS_QUERY_EXECUTOR = Executors.newFixedThreadPool(12);
+
public static final int DEFAULT_PORT_XMPP = 5222;
private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
private static final String STARTTLS_SERVICE = "_xmpp-client";
- private static XmppConnectionService SERVICE = null;
-
-
- public static void init(XmppConnectionService service) {
- Resolver.SERVICE = service;
- DNSClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE);
- DNSClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE);
- DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service));
- final AbstractDNSClient client = ResolverApi.INSTANCE.getClient();
- if (client instanceof ReliableDNSClient) {
- disableHardcodedDnsServers((ReliableDNSClient) client);
- }
- }
-
- private static void disableHardcodedDnsServers(ReliableDNSClient reliableDNSClient) {
- try {
- final Field dnsClientField = ReliableDNSClient.class.getDeclaredField("dnsClient");
- dnsClientField.setAccessible(true);
- final DNSClient dnsClient = (DNSClient) dnsClientField.get(reliableDNSClient);
- if (dnsClient != null) {
- dnsClient.getDataSource().setTimeout(3000);
- }
- final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers");
- useHardcodedDnsServers.setAccessible(true);
- useHardcodedDnsServers.setBoolean(dnsClient, false);
- } catch (NoSuchFieldException | IllegalAccessException e) {
- Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e);
- }
- }
-
public static List<Result> fromHardCoded(final String hostname, final int port) {
final Result result = new Result();
- result.hostname = DNSName.from(hostname);
+ result.hostname = DnsName.from(hostname);
result.port = port;
result.directTls = useDirectTls(port);
result.authenticated = true;
@@ -92,221 +90,236 @@ public class Resolver {
}
public static void checkDomain(final Jid jid) {
- DNSName.from(jid.getDomain());
+ DnsName.from(jid.getDomain());
}
public static boolean invalidHostname(final String hostname) {
try {
- DNSName.from(hostname);
+ DnsName.from(hostname);
return false;
} catch (IllegalArgumentException e) {
return true;
}
}
- public static void clearCache() {
- final AbstractDNSClient client = ResolverApi.INSTANCE.getClient();
- final DNSCache dnsCache = client.getCache();
- if (dnsCache instanceof LRUCache) {
- Log.d(Config.LOGTAG,"clearing DNS cache");
- ((LRUCache) dnsCache).clear();
- }
- }
-
+ public static void clearCache() {}
public static boolean useDirectTls(final int port) {
return port == 443 || port == 5223;
}
public static List<Result> resolve(final String domain) {
- final List<Result> ipResults = fromIpAddress(domain);
- if (ipResults.size() > 0) {
+ final List<Result> ipResults = fromIpAddress(domain);
+ if (!ipResults.isEmpty()) {
return ipResults;
}
- final List<Result> results = new ArrayList<>();
- final List<Result> fallbackResults = new ArrayList<>();
- final Thread[] threads = new Thread[3];
- threads[0] = new Thread(() -> {
- try {
- final List<Result> list = resolveSrv(domain, true);
- synchronized (results) {
- results.addAll(list);
- }
- } catch (final Throwable throwable) {
- if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable);
- }
- }
- });
- threads[1] = new Thread(() -> {
- try {
- final List<Result> list = resolveSrv(domain, false);
- synchronized (results) {
- results.addAll(list);
- }
- } catch (final Throwable throwable) {
- if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable);
- }
- }
- });
- threads[2] = new Thread(() -> {
- List<Result> list = resolveNoSrvRecords(DNSName.from(domain), true);
- synchronized (fallbackResults) {
- fallbackResults.addAll(list);
- }
- });
- for (final Thread thread : threads) {
- thread.start();
- }
+
+ final var startTls = resolveSrvAsFuture(domain, false);
+ final var directTls = resolveSrvAsFuture(domain, true);
+
+ final var combined = merge(ImmutableList.of(startTls, directTls));
+
+ final var combinedWithFallback =
+ Futures.transformAsync(
+ combined,
+ results -> {
+ if (results.isEmpty()) {
+ return resolveNoSrvAsFuture(DnsName.from(domain), true);
+ } else {
+ return Futures.immediateFuture(results);
+ }
+ },
+ MoreExecutors.directExecutor());
+ final var orderedFuture =
+ Futures.transform(
+ combinedWithFallback,
+ all -> Ordering.from(RESULT_COMPARATOR).immutableSortedCopy(all),
+ MoreExecutors.directExecutor());
try {
- threads[0].join();
- threads[1].join();
- if (results.size() > 0) {
- threads[2].interrupt();
- synchronized (results) {
- Collections.sort(results);
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results);
- return results;
- }
- } else {
- threads[2].join();
- synchronized (fallbackResults) {
- Collections.sort(fallbackResults);
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults);
- return fallbackResults;
- }
- }
- } catch (InterruptedException e) {
- for (Thread thread : threads) {
- thread.interrupt();
- }
+ final var ordered = orderedFuture.get();
+ Log.d(Config.LOGTAG, "Resolver (" + ordered.size() + "): " + ordered);
+ return ordered;
+ } catch (final ExecutionException e) {
+ Log.d(Config.LOGTAG, "error resolving DNS", e);
+ return Collections.emptyList();
+ } catch (final InterruptedException e) {
+ Log.d(Config.LOGTAG, "DNS resolution interrupted");
return Collections.emptyList();
}
}
- private static List<Result> fromIpAddress(String domain) {
- if (!IP.matches(domain)) {
- return Collections.emptyList();
- }
- try {
- Result result = new Result();
- result.ip = InetAddress.getByName(domain);
+ private static List<Result> fromIpAddress(final String domain) {
+ if (IP.matches(domain)) {
+ final InetAddress inetAddress;
+ try {
+ inetAddress = InetAddress.getByName(domain);
+ } catch (final UnknownHostException e) {
+ return Collections.emptyList();
+ }
+ final Result result = new Result();
+ result.ip = inetAddress;
result.port = DEFAULT_PORT_XMPP;
return Collections.singletonList(result);
- } catch (UnknownHostException e) {
+ } else {
return Collections.emptyList();
}
}
- private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
- DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
- ResolverResult<SRV> result = resolveWithFallback(dnsName, SRV.class);
- final List<Result> results = new ArrayList<>();
- final List<Thread> threads = new ArrayList<>();
- for (SRV record : result.getAnswersOrEmptySet()) {
- if (record.name.length() == 0 && record.priority == 0) {
- continue;
- }
- threads.add(new Thread(() -> {
- final List<Result> ipv4s = resolveIp(record, A.class, result.isAuthenticData(), directTls);
- if (ipv4s.size() == 0) {
- Result resolverResult = Result.fromRecord(record, directTls);
- resolverResult.authenticated = result.isAuthenticData();
- ipv4s.add(resolverResult);
- }
- synchronized (results) {
- results.addAll(ipv4s);
- }
-
- }));
- threads.add(new Thread(() -> {
- final List<Result> ipv6s = resolveIp(record, AAAA.class, result.isAuthenticData(), directTls);
- synchronized (results) {
- results.addAll(ipv6s);
- }
- }));
- }
- for (Thread thread : threads) {
- thread.start();
- }
- for (Thread thread : threads) {
- try {
- thread.join();
- } catch (InterruptedException e) {
- return Collections.emptyList();
- }
- }
- return results;
+ private static ListenableFuture<List<Result>> resolveSrvAsFuture(
+ final String domain, final boolean directTls) {
+ final DnsName dnsName =
+ DnsName.from(
+ (directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
+ final var resultFuture = resolveAsFuture(dnsName, SRV.class);
+ return Futures.transformAsync(
+ resultFuture,
+ result -> resolveIpsAsFuture(result, directTls),
+ MoreExecutors.directExecutor());
}
- private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
- List<Result> list = new ArrayList<>();
- try {
- ResolverResult<D> results = resolveWithFallback(srv.name, type, authenticated);
- for (D record : results.getAnswersOrEmptySet()) {
- Result resolverResult = Result.fromRecord(srv, directTls);
- resolverResult.authenticated = results.isAuthenticData() && authenticated; //TODO technically it doesnβt matter if the IP was authenticated
- resolverResult.ip = record.getInetAddress();
- list.add(resolverResult);
+ @NonNull
+ private static ListenableFuture<List<Result>> resolveIpsAsFuture(
+ final ResolverResult<SRV> srvResolverResult, final boolean directTls) {
+ final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
+ new ImmutableList.Builder<>();
+ for (final SRV record : srvResolverResult.getAnswersOrEmptySet()) {
+ if (record.target.length() == 0 && record.priority == 0) {
+ continue;
}
- } catch (Throwable t) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage());
+ final var ipv4sRaw =
+ resolveIpsAsFuture(
+ record, A.class, srvResolverResult.isAuthenticData(), directTls);
+ final var ipv4s =
+ Futures.transform(
+ ipv4sRaw,
+ results -> {
+ if (results.isEmpty()) {
+ final Result resolverResult =
+ Result.fromRecord(record, directTls);
+ resolverResult.authenticated =
+ srvResolverResult.isAuthenticData();
+ return Collections.singletonList(resolverResult);
+ } else {
+ return results;
+ }
+ },
+ MoreExecutors.directExecutor());
+ final var ipv6s =
+ resolveIpsAsFuture(
+ record, AAAA.class, srvResolverResult.isAuthenticData(), directTls);
+ futuresBuilder.add(ipv4s);
+ futuresBuilder.add(ipv6s);
}
- return list;
+ final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
+ return merge(futures);
}
- private static List<Result> resolveNoSrvRecords(DNSName dnsName, boolean withCnames) {
- final List<Result> results = new ArrayList<>();
- try {
- for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) {
- results.add(Result.createDefault(dnsName, a.getInetAddress()));
- }
- for (AAAA aaaa : resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) {
- results.add(Result.createDefault(dnsName, aaaa.getInetAddress()));
- }
- if (results.size() == 0 && withCnames) {
- for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
- results.addAll(resolveNoSrvRecords(cname.name, false));
- }
- }
- } catch (final Throwable throwable) {
- if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable);
- }
- }
- results.add(Result.createDefault(dnsName));
- return results;
+ private static ListenableFuture<List<Result>> merge(
+ final Collection<ListenableFuture<List<Result>>> futures) {
+ return Futures.transform(
+ Futures.successfulAsList(futures),
+ lists -> {
+ final var builder = new ImmutableList.Builder<Result>();
+ for (final Collection<Result> list : lists) {
+ if (list == null) {
+ continue;
+ }
+ builder.addAll(list);
+ }
+ return builder.build();
+ },
+ MoreExecutors.directExecutor());
}
- private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type) throws IOException {
- return resolveWithFallback(dnsName, type, validateHostname());
+ private static <D extends InternetAddressRR<?>>
+ ListenableFuture<List<Result>> resolveIpsAsFuture(
+ final SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
+ final var resultFuture = resolveAsFuture(srv.target, type);
+ return Futures.transform(
+ resultFuture,
+ result -> {
+ final var builder = new ImmutableList.Builder<Result>();
+ for (D record : result.getAnswersOrEmptySet()) {
+ Result resolverResult = Result.fromRecord(srv, directTls);
+ resolverResult.authenticated =
+ result.isAuthenticData()
+ && authenticated; // TODO technically it does not matter if
+ // the IP
+ // was authenticated
+ resolverResult.ip = record.getInetAddress();
+ builder.add(resolverResult);
+ }
+ return builder.build();
+ },
+ MoreExecutors.directExecutor());
}
- private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type, boolean validateHostname) throws IOException {
- final Question question = new Question(dnsName, Record.TYPE.getType(type));
- if (!validateHostname) {
- final AndroidDNSClient androidDNSClient = new AndroidDNSClient(SERVICE);
- final ResolverApi resolverApi = new ResolverApi(androidDNSClient);
- return resolverApi.resolve(question);
- }
- try {
- return DnssecResolverApi.INSTANCE.resolveDnssecReliable(question);
- } catch (DNSSECResultNotAuthenticException e) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", e);
- } catch (IOException e) {
- throw e;
- } catch (Throwable throwable) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable);
+ private static ListenableFuture<List<Result>> resolveNoSrvAsFuture(
+ final DnsName dnsName, boolean cName) {
+ final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
+ new ImmutableList.Builder<>();
+ ListenableFuture<List<Result>> aRecordResults =
+ Futures.transform(
+ resolveAsFuture(dnsName, A.class),
+ result ->
+ Lists.transform(
+ ImmutableList.copyOf(result.getAnswersOrEmptySet()),
+ a -> Result.createDefault(dnsName, a.getInetAddress())),
+ MoreExecutors.directExecutor());
+ futuresBuilder.add(aRecordResults);
+ ListenableFuture<List<Result>> aaaaRecordResults =
+ Futures.transform(
+ resolveAsFuture(dnsName, AAAA.class),
+ result ->
+ Lists.transform(
+ ImmutableList.copyOf(result.getAnswersOrEmptySet()),
+ aaaa ->
+ Result.createDefault(
+ dnsName, aaaa.getInetAddress())),
+ MoreExecutors.directExecutor());
+ futuresBuilder.add(aaaaRecordResults);
+ if (cName) {
+ ListenableFuture<List<Result>> cNameRecordResults =
+ Futures.transformAsync(
+ resolveAsFuture(dnsName, CNAME.class),
+ result -> {
+ Collection<ListenableFuture<List<Result>>> test =
+ Lists.transform(
+ ImmutableList.copyOf(result.getAnswersOrEmptySet()),
+ cname -> resolveNoSrvAsFuture(cname.target, false));
+ return merge(test);
+ },
+ MoreExecutors.directExecutor());
+ futuresBuilder.add(cNameRecordResults);
}
- return ResolverApi.INSTANCE.resolve(question);
+ final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
+ final var noSrvFallbacks = merge(futures);
+ return Futures.transform(
+ noSrvFallbacks,
+ results -> {
+ if (results.isEmpty()) {
+ return Collections.singletonList(Result.createDefault(dnsName));
+ } else {
+ return results;
+ }
+ },
+ MoreExecutors.directExecutor());
}
- private static boolean validateHostname() {
- return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname);
+ private static <D extends Data> ListenableFuture<ResolverResult<D>> resolveAsFuture(
+ final DnsName dnsName, final Class<D> type) {
+ return Futures.submit(
+ () -> {
+ final Question question = new Question(dnsName, Record.TYPE.getType(type));
+ final AndroidDNSClient androidDNSClient =
+ new AndroidDNSClient(Conversations.getContext());
+ final DnsQueryResult dnsQueryResult = androidDNSClient.query(question);
+ return new ResolverResult<D>(question, dnsQueryResult, null);
+ },
+ DNS_QUERY_EXECUTOR);
}
- public static class Result implements Comparable<Result> {
+ public static class Result {
public static final String DOMAIN = "domain";
public static final String IP = "ip";
public static final String HOSTNAME = "hostname";
@@ -315,7 +328,7 @@ public class Resolver {
public static final String DIRECT_TLS = "directTls";
public static final String AUTHENTICATED = "authenticated";
private InetAddress ip;
- private DNSName hostname;
+ private DnsName hostname;
private int port = DEFAULT_PORT_XMPP;
private boolean directTls = false;
private boolean authenticated = false;
@@ -330,7 +343,7 @@ public class Resolver {
return result;
}
- static Result createDefault(DNSName hostname, InetAddress ip) {
+ static Result createDefault(DnsName hostname, InetAddress ip) {
Result result = new Result();
result.port = DEFAULT_PORT_XMPP;
result.hostname = hostname;
@@ -338,23 +351,24 @@ public class Resolver {
return result;
}
- static Result createDefault(DNSName hostname) {
+ static Result createDefault(DnsName hostname) {
return createDefault(hostname, null);
}
public static Result fromCursor(Cursor cursor) {
final Result result = new Result();
try {
- result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
+ result.ip =
+ InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndexOrThrow(IP)));
} catch (UnknownHostException e) {
result.ip = null;
}
- final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME));
- result.hostname = hostname == null ? null : DNSName.from(hostname);
- result.port = cursor.getInt(cursor.getColumnIndex(PORT));
- result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
- result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
- result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
+ final String hostname = cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME));
+ result.hostname = hostname == null ? null : DnsName.from(hostname);
+ result.port = cursor.getInt(cursor.getColumnIndexOrThrow(PORT));
+ result.priority = cursor.getInt(cursor.getColumnIndexOrThrow(PRIORITY));
+ result.authenticated = cursor.getInt(cursor.getColumnIndexOrThrow(AUTHENTICATED)) > 0;
+ result.directTls = cursor.getInt(cursor.getColumnIndexOrThrow(DIRECT_TLS)) > 0;
return result;
}
@@ -362,26 +376,18 @@ public class Resolver {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
-
Result result = (Result) o;
-
- if (port != result.port) return false;
- if (directTls != result.directTls) return false;
- if (authenticated != result.authenticated) return false;
- if (priority != result.priority) return false;
- if (ip != null ? !ip.equals(result.ip) : result.ip != null) return false;
- return hostname != null ? hostname.equals(result.hostname) : result.hostname == null;
+ return port == result.port
+ && directTls == result.directTls
+ && authenticated == result.authenticated
+ && priority == result.priority
+ && Objects.equal(ip, result.ip)
+ && Objects.equal(hostname, result.hostname);
}
@Override
public int hashCode() {
- int result = ip != null ? ip.hashCode() : 0;
- result = 31 * result + (hostname != null ? hostname.hashCode() : 0);
- result = 31 * result + port;
- result = 31 * result + (directTls ? 1 : 0);
- result = 31 * result + (authenticated ? 1 : 0);
- result = 31 * result + priority;
- return result;
+ return Objects.hashCode(ip, hostname, port, directTls, authenticated, priority);
}
public InetAddress getIp() {
@@ -392,7 +398,7 @@ public class Resolver {
return port;
}
- public DNSName getHostname() {
+ public DnsName getHostname() {
return hostname;
}
@@ -405,38 +411,16 @@ public class Resolver {
}
@Override
+ @NonNull
public String toString() {
- return "Result{" +
- "ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' +
- ", hostame='" + (hostname == null ? null : hostname.toString()) + '\'' +
- ", port=" + port +
- ", directTls=" + directTls +
- ", authenticated=" + authenticated +
- ", priority=" + priority +
- '}';
- }
-
- @Override
- public int compareTo(@NonNull Result result) {
- if (result.priority == priority) {
- if (directTls == result.directTls) {
- if (ip == null && result.ip == null) {
- return 0;
- } else if (ip != null && result.ip != null) {
- if (ip instanceof Inet4Address && result.ip instanceof Inet4Address) {
- return 0;
- } else {
- return ip instanceof Inet4Address ? -1 : 1;
- }
- } else {
- return ip != null ? -1 : 1;
- }
- } else {
- return directTls ? -1 : 1;
- }
- } else {
- return priority - result.priority;
- }
+ return MoreObjects.toStringHelper(this)
+ .add("ip", ip)
+ .add("hostname", hostname)
+ .add("port", port)
+ .add("directTls", directTls)
+ .add("authenticated", authenticated)
+ .add("priority", priority)
+ .toString();
}
public ContentValues toContentValues() {
@@ -482,7 +466,7 @@ public class Resolver {
return null;
}
try {
- result.hostname = DNSName.from(hostPart.trim());
+ result.hostname = DnsName.from(hostPart.trim());
} catch (final Exception e) {
return null;
}
@@ -499,7 +483,7 @@ public class Resolver {
result.ip = inetAddress;
} else {
try {
- result.hostname = DNSName.from(hostname);
+ result.hostname = DnsName.from(hostname);
} catch (final Exception e) {
return null;
}
@@ -509,5 +493,4 @@ public class Resolver {
return result;
}
}
-
}
@@ -354,7 +354,7 @@ public class XmppConnection implements Runnable {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Thread was interrupted");
return;
}
- if (results.size() == 0) {
+ if (results.isEmpty()) {
Log.e(
Config.LOGTAG,
account.getJid().asBareJid() + ": Resolver results were empty");