Upgrade minidns, verify DNSSEC when possible, show in UI

Stephen Paul Weber created

Change summary

build.gradle                                                                |   2 
src/cheogram/res/drawable/shield.xml                                        |  10 
src/cheogram/res/drawable/shield_question.xml                               |  11 
src/main/java/de/gultsch/minidns/AndroidDNSClient.java                      | 221 
src/main/java/de/gultsch/minidns/DNSServer.java                             | 104 
src/main/java/de/gultsch/minidns/DNSSocket.java                             | 199 
src/main/java/de/gultsch/minidns/NetworkDataSource.java                     | 160 
src/main/java/de/gultsch/minidns/Transport.java                             |  23 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java            |   9 
src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java         |   5 
src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java |  19 
src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java  |  12 
src/main/java/eu/siacs/conversations/utils/Resolver.java                    | 149 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java               |   6 
src/main/res/layout/account_row.xml                                         |  27 
src/main/res/layout/activity_edit_account.xml                               |  37 
src/main/res/xml/preferences.xml                                            |   5 
17 files changed, 182 insertions(+), 817 deletions(-)

Detailed changes

build.gradle πŸ”—

@@ -73,7 +73,7 @@ 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-hla:1.0.4'
     implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
     implementation 'org.whispersystems:signal-protocol-android:2.6.2'
     implementation "com.wefika:flowlayout:0.4.1"

src/cheogram/res/drawable/shield.xml πŸ”—

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M480,880Q341,845 250.5,720.5Q160,596 160,444L160,200L480,80L800,200L800,444Q800,596 709.5,720.5Q619,845 480,880ZM480,796Q584,763 652,664Q720,565 720,444L720,255L480,165L240,255L240,444Q240,565 308,664Q376,763 480,796ZM480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>

src/cheogram/res/drawable/shield_question.xml πŸ”—

@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal"
+    android:autoMirrored="true">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M480,880Q341,845 250.5,720.5Q160,596 160,444L160,200L480,80L800,200L800,444Q800,596 709.5,720.5Q619,845 480,880ZM480,796Q584,763 652,664Q720,565 720,444L720,255L480,165L240,255L240,444Q240,565 308,664Q376,763 480,796ZM480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480ZM480,680Q497,680 509.5,667.5Q522,655 522,638Q522,621 509.5,608.5Q497,596 480,596Q463,596 450.5,608.5Q438,621 438,638Q438,655 450.5,667.5Q463,680 480,680ZM451,552L511,552Q511,541 511,530Q511,519 516,509Q522,495 532,485.5Q542,476 553,466Q570,449 582.5,428Q595,407 595,382Q595,337 560.5,308.5Q526,280 480,280Q440,280 408.5,303Q377,326 366,364L420,386Q426,366 442.5,352Q459,338 480,338Q502,338 518.5,351Q535,364 535,384Q535,401 524.5,415.5Q514,430 501,442Q489,453 477,464.5Q465,476 458,491Q451,505 451,520.5Q451,536 451,552Z"/>
+</vector>

src/main/java/de/gultsch/minidns/AndroidDNSClient.java πŸ”—

@@ -1,221 +0,0 @@
-package de.gultsch.minidns;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.collection.LruCache;
-
-import com.google.common.base.Objects;
-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 eu.siacs.conversations.Config;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.time.Duration;
-import java.util.Collections;
-import java.util.List;
-
-public class AndroidDNSClient extends AbstractDNSClient {
-
-    private static final long DNS_MAX_TTL = 86_400L;
-
-    private static final LruCache<QuestionServerTuple, DNSMessage> QUERY_CACHE =
-            new LruCache<>(1024);
-    private final Context context;
-    private final NetworkDataSource networkDataSource = new NetworkDataSource();
-    private boolean askForDnssec = false;
-
-    public AndroidDNSClient(final Context context) {
-        super();
-        this.setDataSource(networkDataSource);
-        this.context = context;
-    }
-
-    private static String getPrivateDnsServerName(final LinkProperties linkProperties) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-            return linkProperties.getPrivateDnsServerName();
-        } else {
-            return null;
-        }
-    }
-
-    private static boolean isPrivateDnsActive(final LinkProperties linkProperties) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-            return linkProperties.isPrivateDnsActive();
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    protected DNSMessage.Builder newQuestion(final DNSMessage.Builder message) {
-        message.setRecursionDesired(true);
-        message.getEdnsBuilder()
-                .setUdpPayloadSize(networkDataSource.getUdpPayloadSize())
-                .setDnssecOk(askForDnssec);
-        return message;
-    }
-
-    @Override
-    protected DNSMessage 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);
-            if (cachedResponse != null) {
-                return cachedResponse;
-            }
-            final DNSMessage response = this.networkDataSource.query(question, dnsServer);
-            if (response == null) {
-                continue;
-            }
-            switch (response.responseCode) {
-                case NO_ERROR:
-                case NX_DOMAIN:
-                    break;
-                default:
-                    continue;
-            }
-            cacheQuery(cacheKey, response);
-            return response;
-        }
-        return null;
-    }
-
-    public boolean isAskForDnssec() {
-        return askForDnssec;
-    }
-
-    public void setAskForDnssec(boolean askForDnssec) {
-        this.askForDnssec = askForDnssec;
-    }
-
-    private List<DNSServer> getDNSServers() {
-        final ImmutableList.Builder<DNSServer> dnsServerBuilder = new ImmutableList.Builder<>();
-        final ConnectivityManager connectivityManager =
-                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        final Network[] networks = getActiveNetworks(connectivityManager);
-        for (final Network network : networks) {
-            final LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
-            if (linkProperties == null) {
-                continue;
-            }
-            final String privateDnsServerName = getPrivateDnsServerName(linkProperties);
-            if (Strings.isNullOrEmpty(privateDnsServerName)) {
-                final boolean isPrivateDns = isPrivateDnsActive(linkProperties);
-                for (final InetAddress dnsServer : linkProperties.getDnsServers()) {
-                    if (isPrivateDns) {
-                        dnsServerBuilder.add(new DNSServer(dnsServer, Transport.TLS));
-                    } else {
-                        dnsServerBuilder.add(new DNSServer(dnsServer));
-                    }
-                }
-            } else {
-                dnsServerBuilder.add(new DNSServer(privateDnsServerName, Transport.TLS));
-            }
-        }
-        return dnsServerBuilder.build();
-    }
-
-    private Network[] getActiveNetworks(final ConnectivityManager connectivityManager) {
-        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) {
-                return new Network[] {activeNetwork};
-            }
-        }
-        return connectivityManager.getAllNetworks();
-    }
-
-    private DNSMessage queryCache(final QuestionServerTuple key) {
-        final DNSMessage cachedResponse;
-        synchronized (QUERY_CACHE) {
-            cachedResponse = QUERY_CACHE.get(key);
-            if (cachedResponse == null) {
-                return null;
-            }
-            final long expiresIn = expiresIn(cachedResponse);
-            if (expiresIn < 0) {
-                QUERY_CACHE.remove(key);
-                return null;
-            }
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                Log.d(
-                        Config.LOGTAG,
-                        "DNS query came from cache. expires in " + Duration.ofMillis(expiresIn));
-            }
-        }
-        return cachedResponse;
-    }
-
-    private void cacheQuery(final QuestionServerTuple key, final DNSMessage response) {
-        if (response.receiveTimestamp <= 0) {
-            return;
-        }
-        synchronized (QUERY_CACHE) {
-            QUERY_CACHE.put(key, response);
-        }
-    }
-
-    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;
-            if (authoritySection == null || authoritySection.isEmpty()) {
-                return 0;
-            } else {
-                return Collections.min(Collections2.transform(authoritySection, d -> d.ttl));
-            }
-
-        } else {
-            return Collections.min(Collections2.transform(answerSection, d -> d.ttl));
-        }
-    }
-
-    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) {
-        return expiresAt(dnsMessage) - System.currentTimeMillis();
-    }
-
-    private static class QuestionServerTuple {
-        private final DNSServer dnsServer;
-        private final DNSMessage question;
-
-        private QuestionServerTuple(final DNSServer dnsServer, final DNSMessage question) {
-            this.dnsServer = dnsServer;
-            this.question = question.asNormalizedVersion();
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            QuestionServerTuple that = (QuestionServerTuple) o;
-            return Objects.equal(dnsServer, that.dnsServer)
-                    && Objects.equal(question, that.question);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(dnsServer, question);
-        }
-    }
-}

src/main/java/de/gultsch/minidns/DNSServer.java πŸ”—

@@ -1,104 +0,0 @@
-package de.gultsch.minidns;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Iterables;
-
-import java.net.InetAddress;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-
-public final class DNSServer {
-
-    public final InetAddress inetAddress;
-    public final String hostname;
-    public final int port;
-    public final List<Transport> transports;
-
-    public DNSServer(InetAddress inetAddress, Integer port, Transport transport) {
-        this.inetAddress = inetAddress;
-        this.port = port == null ? 0 : port;
-        this.transports = Collections.singletonList(transport);
-        this.hostname = null;
-    }
-
-    public DNSServer(final String hostname, final Integer port, final Transport transport) {
-        Preconditions.checkArgument(
-                Arrays.asList(Transport.HTTPS, Transport.TLS).contains(transport),
-                "hostname validation only works with TLS based transports");
-        this.hostname = hostname;
-        this.port = port == null ? 0 : port;
-        this.transports = Collections.singletonList(transport);
-        this.inetAddress = null;
-    }
-
-    public DNSServer(final String hostname, final Transport transport) {
-        this(hostname, Transport.DEFAULT_PORTS.get(transport), transport);
-    }
-
-    public DNSServer(InetAddress inetAddress, Transport transport) {
-        this(inetAddress, Transport.DEFAULT_PORTS.get(transport), transport);
-    }
-
-    public DNSServer(final InetAddress inetAddress) {
-        this(inetAddress, 53, Arrays.asList(Transport.UDP, Transport.TCP));
-    }
-
-    public DNSServer(final InetAddress inetAddress, int port, List<Transport> transports) {
-        this(inetAddress, null, port, transports);
-    }
-
-    private DNSServer(
-            final InetAddress inetAddress,
-            final String hostname,
-            final int port,
-            final List<Transport> transports) {
-        this.inetAddress = inetAddress;
-        this.hostname = hostname;
-        this.port = port;
-        this.transports = transports;
-    }
-
-    public Transport uniqueTransport() {
-        return Iterables.getOnlyElement(this.transports);
-    }
-
-    public DNSServer asUniqueTransport(final Transport transport) {
-        Preconditions.checkArgument(
-                this.transports.contains(transport),
-                "This DNS server does not have transport ",
-                transport);
-        return new DNSServer(inetAddress, hostname, port, Collections.singletonList(transport));
-    }
-
-    @Override
-    @Nonnull
-    public String toString() {
-        return MoreObjects.toStringHelper(this)
-                .add("inetAddress", inetAddress)
-                .add("hostname", hostname)
-                .add("port", port)
-                .add("transports", transports)
-                .toString();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        DNSServer dnsServer = (DNSServer) o;
-        return port == dnsServer.port
-                && Objects.equal(inetAddress, dnsServer.inetAddress)
-                && Objects.equal(hostname, dnsServer.hostname)
-                && Objects.equal(transports, dnsServer.transports);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(inetAddress, hostname, port, transports);
-    }
-}

src/main/java/de/gultsch/minidns/DNSSocket.java πŸ”—

@@ -1,199 +0,0 @@
-package de.gultsch.minidns;
-
-import android.util.Log;
-
-import com.google.common.base.Preconditions;
-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 java.io.Closeable;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-
-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 Socket socket;
-    private final DataInputStream dataInputStream;
-    private final DataOutputStream dataOutputStream;
-
-    private DNSSocket(
-            final Socket socket,
-            final DataInputStream dataInputStream,
-            final DataOutputStream dataOutputStream) {
-        this.socket = socket;
-        this.dataInputStream = dataInputStream;
-        this.dataOutputStream = dataOutputStream;
-        new Thread(this::readDNSMessages).start();
-    }
-
-    private void readDNSMessages() {
-        try {
-            while (socket.isConnected()) {
-                final DNSMessage response = readDNSMessage();
-                final SettableFuture<DNSMessage> future;
-                synchronized (inFlightQueries) {
-                    future = inFlightQueries.remove(response.id);
-                }
-                if (future != null) {
-                    future.set(response);
-                } else {
-                    Log.e(Config.LOGTAG, "no in flight query found for response id " + response.id);
-                }
-            }
-            evictInFlightQueries(new EOFException());
-        } catch (final IOException e) {
-            evictInFlightQueries(e);
-        }
-    }
-
-    private void evictInFlightQueries(final Exception e) {
-        synchronized (inFlightQueries) {
-            final Iterator<Map.Entry<Integer, SettableFuture<DNSMessage>>> iterator =
-                    inFlightQueries.entrySet().iterator();
-            while (iterator.hasNext()) {
-                final Map.Entry<Integer, SettableFuture<DNSMessage>> entry = iterator.next();
-                entry.getValue().setException(e);
-                iterator.remove();
-            }
-        }
-    }
-
-    private static DNSSocket of(final Socket socket) throws IOException {
-        final DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
-        final DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
-        return new DNSSocket(socket, dataInputStream, dataOutputStream);
-    }
-
-    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");
-        }
-    }
-
-    private static DNSSocket connectTcpSocket(final DNSServer dnsServer) throws IOException {
-        Preconditions.checkArgument(dnsServer.uniqueTransport() == Transport.TCP);
-        final SocketAddress socketAddress =
-                new InetSocketAddress(dnsServer.inetAddress, dnsServer.port);
-        final Socket socket = new Socket();
-        socket.connect(socketAddress, QUERY_TIMEOUT / 2);
-        socket.setSoTimeout(QUERY_TIMEOUT);
-        return DNSSocket.of(socket);
-    }
-
-    private static DNSSocket connectTlsSocket(final DNSServer dnsServer) throws IOException {
-        Preconditions.checkArgument(dnsServer.uniqueTransport() == Transport.TLS);
-        final SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
-        final SSLSocket sslSocket = (SSLSocket) factory.createSocket();
-        if (Strings.isNullOrEmpty(dnsServer.hostname)) {
-            final SocketAddress socketAddress =
-                    new InetSocketAddress(dnsServer.inetAddress, dnsServer.port);
-            sslSocket.connect(socketAddress, QUERY_TIMEOUT / 2);
-            sslSocket.setSoTimeout(QUERY_TIMEOUT);
-            sslSocket.startHandshake();
-        } else {
-            final SocketAddress socketAddress = new InetSocketAddress(dnsServer.hostname, dnsServer.port);
-            sslSocket.connect(socketAddress, QUERY_TIMEOUT / 2);
-            sslSocket.setSoTimeout(QUERY_TIMEOUT);
-            sslSocket.startHandshake();
-            final SSLSession session = sslSocket.getSession();
-            final Certificate[] peerCertificates = session.getPeerCertificates();
-            if (peerCertificates.length == 0 || !(peerCertificates[0] instanceof X509Certificate)) {
-                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");
-            }
-        }
-        return DNSSocket.of(sslSocket);
-    }
-
-    public DNSMessage query(final DNSMessage query) throws IOException, InterruptedException {
-        try {
-            return queryAsync(query).get(QUERY_TIMEOUT, TimeUnit.MILLISECONDS);
-        } catch (final ExecutionException e) {
-            final Throwable cause = e.getCause();
-            if (cause instanceof IOException) {
-                throw (IOException) cause;
-            } else {
-                throw new IOException(e);
-            }
-        } catch (final TimeoutException e) {
-            throw new IOException(e);
-        }
-    }
-
-    public ListenableFuture<DNSMessage> queryAsync(final DNSMessage query)
-            throws InterruptedException, IOException {
-        final SettableFuture<DNSMessage> responseFuture = SettableFuture.create();
-        synchronized (this.inFlightQueries) {
-            this.inFlightQueries.put(query.id, responseFuture);
-        }
-        this.semaphore.acquire();
-        try {
-            query.writeTo(this.dataOutputStream);
-            this.dataOutputStream.flush();
-        } finally {
-            this.semaphore.release();
-        }
-        return responseFuture;
-    }
-
-    private DNSMessage readDNSMessage() throws IOException {
-        final int length = this.dataInputStream.readUnsignedShort();
-        byte[] data = new byte[length];
-        int read = 0;
-        while (read < length) {
-            read += this.dataInputStream.read(data, read, length - read);
-        }
-        return new DNSMessage(data);
-    }
-
-    @Override
-    public void close() throws IOException {
-        this.socket.close();
-    }
-
-    public void closeQuietly() {
-        try {
-            this.socket.close();
-        } catch (final IOException ignored) {
-
-        }
-    }
-}

src/main/java/de/gultsch/minidns/NetworkDataSource.java πŸ”—

@@ -1,160 +0,0 @@
-package de.gultsch.minidns;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-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 java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-
-public class NetworkDataSource extends DNSDataSource {
-
-    private static final LoadingCache<DNSServer, DNSSocket> socketCache =
-            CacheBuilder.newBuilder()
-                    .removalListener(
-                            (RemovalListener<DNSServer, DNSSocket>)
-                                    notification -> {
-                                        final DNSServer dnsServer = notification.getKey();
-                                        final DNSSocket dnsSocket = notification.getValue();
-                                        if (dnsSocket == null) {
-                                            return;
-                                        }
-                                        Log.d(Config.LOGTAG, "closing connection to " + dnsServer);
-                                        dnsSocket.closeQuietly();
-                                    })
-                    .expireAfterAccess(5, TimeUnit.MINUTES)
-                    .build(
-                            new CacheLoader<DNSServer, DNSSocket>() {
-                                @Override
-                                @NonNull
-                                public DNSSocket load(@NonNull final DNSServer dnsServer)
-                                        throws Exception {
-                                    Log.d(Config.LOGTAG, "establishing connection to " + dnsServer);
-                                    return DNSSocket.connect(dnsServer);
-                                }
-                            });
-
-    private static List<Transport> transportsForPort(final int port) {
-        final ImmutableList.Builder<Transport> transportBuilder = new ImmutableList.Builder<>();
-        for (final Map.Entry<Transport, Integer> entry : Transport.DEFAULT_PORTS.entrySet()) {
-            if (entry.getValue().equals(port)) {
-                transportBuilder.add(entry.getKey());
-            }
-        }
-        return transportBuilder.build();
-    }
-
-    @Override
-    public DNSMessage query(final DNSMessage message, final InetAddress address, final int port)
-            throws IOException {
-        final List<Transport> transports = transportsForPort(port);
-        Log.w(
-                Config.LOGTAG,
-                "using legacy DataSource interface. guessing transports "
-                        + transports
-                        + " from port");
-        if (transports.isEmpty()) {
-            throw new IOException(String.format("No transports found for port %d", port));
-        }
-        return query(message, new DNSServer(address, port, transports));
-    }
-
-    public DNSMessage 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 =
-                        queryWithUniqueTransport(message, dnsServer.asUniqueTransport(transport));
-                if (response != null && !response.truncated) {
-                    return response;
-                }
-            } catch (final IOException e) {
-                ioExceptions.add(e);
-            } catch (final InterruptedException e) {
-                throw new IOException(e);
-            }
-        }
-        MultipleIoException.throwIfRequired(ioExceptions);
-        return null;
-    }
-
-    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));
-        }
-    }
-
-    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];
-        try (final DatagramSocket socket = new DatagramSocket()) {
-            socket.setSoTimeout(timeout);
-            socket.send(request);
-            final DatagramPacket response = new DatagramPacket(buffer, buffer.length);
-            socket.receive(response);
-            DNSMessage dnsMessage = new DNSMessage(response.getData());
-            if (dnsMessage.id != message.id) {
-                throw new MiniDNSException.IdMismatch(message, dnsMessage);
-            }
-            return dnsMessage;
-        }
-    }
-
-    protected DNSMessage queryDnsSocket(final DNSMessage message, final DNSServer dnsServer)
-            throws IOException, InterruptedException {
-        final DNSSocket cachedDnsSocket = socketCache.getIfPresent(dnsServer);
-        if (cachedDnsSocket != null) {
-            try {
-                return cachedDnsSocket.query(message);
-            } catch (final IOException e) {
-                Log.d(
-                        Config.LOGTAG,
-                        "IOException occurred at cached socket. invalidating and falling through to new socket creation");
-                socketCache.invalidate(dnsServer);
-            }
-        }
-        try {
-            return socketCache.get(dnsServer).query(message);
-        } catch (final ExecutionException e) {
-            final Throwable cause = e.getCause();
-            if (cause instanceof IOException) {
-                throw (IOException) cause;
-            } else {
-                throw new IOException(cause);
-            }
-        }
-    }
-}

src/main/java/de/gultsch/minidns/Transport.java πŸ”—

@@ -1,23 +0,0 @@
-package de.gultsch.minidns;
-
-import com.google.common.collect.ImmutableMap;
-
-import java.util.Map;
-
-public enum Transport {
-    UDP,
-    TCP,
-    TLS,
-    HTTPS;
-
-    public static final Map<Transport, Integer> DEFAULT_PORTS;
-
-    static {
-        final ImmutableMap.Builder<Transport, Integer> builder = new ImmutableMap.Builder<>();
-        builder.put(Transport.UDP, 53);
-        builder.put(Transport.TCP, 53);
-        builder.put(Transport.TLS, 853);
-        builder.put(Transport.HTTPS, 443);
-        DEFAULT_PORTS = builder.build();
-    }
-}

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java πŸ”—

@@ -1265,6 +1265,14 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
             } else {
                 this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
             }
+            this.binding.verificationBox.setVisibility(View.VISIBLE);
+            if (mAccount.getXmppConnection() != null && mAccount.getXmppConnection().resolverAuthenticated()) {
+                this.binding.verificationMessage.setText("DNSSEC Verified");
+                this.binding.verificationIndicator.setImageResource(R.drawable.shield);
+            } else {
+                this.binding.verificationMessage.setText("Not DNSSEC Verified");
+                this.binding.verificationIndicator.setImageResource(R.drawable.shield_question);
+            }
         } else {
             final TextInputLayout errorLayout;
             if (this.mAccount.errorStatus()) {
@@ -1287,6 +1295,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
             removeErrorsOnAllBut(errorLayout);
             this.binding.stats.setVisibility(View.GONE);
             this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
+            this.binding.verificationBox.setVisibility(View.GONE);
         }
     }
 

src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java πŸ”—

@@ -69,6 +69,11 @@ public class AccountAdapter extends ArrayAdapter<Account> {
                 viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorError));
                 break;
         }
+        if (account.getXmppConnection() != null && account.getXmppConnection().resolverAuthenticated()) {
+            viewHolder.binding.verificationIndicator.setImageResource(R.drawable.shield);
+        } else {
+            viewHolder.binding.verificationIndicator.setImageResource(R.drawable.shield_question);
+        }
         final boolean isDisabled = (account.getStatus() == Account.State.DISABLED);
         viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener(null);
         viewHolder.binding.tglAccountStatus.setChecked(!isDisabled);

src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java πŸ”—

@@ -19,17 +19,20 @@ 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;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism;
+import org.minidns.dnsserverlookup.AndroidUsingReflection;
+import org.minidns.dnsserverlookup.DnsServerLookupMechanism;
+import org.minidns.util.PlatformDetection;
 
 /**
  * Try to retrieve the list of DNS server by executing getprop.
  */
-public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanism {
+public class AndroidUsingExecLowPriority extends AbstractDnsServerLookupMechanism {
 
-	public static final DNSServerLookupMechanism INSTANCE = new AndroidUsingExecLowPriority();
+	public static final DnsServerLookupMechanism INSTANCE = new AndroidUsingExecLowPriority();
 	public static final int PRIORITY = AndroidUsingReflection.PRIORITY + 1;
 
 	private AndroidUsingExecLowPriority() {
@@ -37,7 +40,7 @@ public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanis
 	}
 
 	@Override
-	public String[] getDnsServerAddresses() {
+	public List<String> getDnsServerAddresses() {
 		try {
 			Process process = Runtime.getRuntime().exec("getprop");
 			InputStream inputStream = process.getInputStream();
@@ -76,7 +79,7 @@ public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanis
 				}
 			}
 			if (server.size() > 0) {
-				return server.toArray(new String[server.size()]);
+				return new ArrayList<>(server);
 			}
 		} catch (IOException e) {
 			LOGGER.log(Level.WARNING, "Exception in findDNSByExec", e);

src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java πŸ”—

@@ -14,10 +14,10 @@ import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.List;
 
-import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism;
-import de.measite.minidns.dnsserverlookup.AndroidUsingExec;
+import org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism;
+import org.minidns.dnsserverlookup.AndroidUsingExec;
 
-public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism {
+public class AndroidUsingLinkProperties extends AbstractDnsServerLookupMechanism {
 
     private final Context context;
 
@@ -33,11 +33,11 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism
 
     @Override
     @TargetApi(21)
-    public String[] getDnsServerAddresses() {
+    public List<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];
+            return new ArrayList<>();
         }
         final Network activeNetwork = getActiveNetwork(connectivityManager);
         final List<String> servers = new ArrayList<>();
@@ -58,7 +58,7 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism
                 servers.addAll(vpnOffset, getIPv4First(linkProperties.getDnsServers()));
             }
         }
-        return servers.toArray(new String[0]);
+        return servers;
     }
 
     @TargetApi(23)

src/main/java/eu/siacs/conversations/utils/Resolver.java πŸ”—

@@ -20,26 +20,26 @@ import java.util.ArrayList;
 import java.util.Collections;
 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 de.gultsch.minidns.AndroidDNSClient;
+import org.minidns.AbstractDnsClient;
+import org.minidns.DnsCache;
+import org.minidns.DnsClient;
+import org.minidns.dnsname.DnsName;
+import org.minidns.dnsmessage.Question;
+import org.minidns.record.Record;
+import org.minidns.cache.LruCache;
+import org.minidns.dnssec.DnssecResultNotAuthenticException;
+import org.minidns.dnsserverlookup.AndroidUsingExec;
+import org.minidns.hla.DnssecResolverApi;
+import org.minidns.hla.ResolverApi;
+import org.minidns.hla.ResolverResult;
+import org.minidns.iterative.ReliableDnsClient;
+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.SRV;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.services.XmppConnectionService;
@@ -57,34 +57,18 @@ public class Resolver {
 
     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);
+        DnsClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE);
+        DnsClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE);
+        DnsClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service));
+        final AbstractDnsClient client = ResolverApi.INSTANCE.getClient();
+        if (client instanceof ReliableDnsClient) {
+            ((ReliableDnsClient) client).setUseHardcodedDnsServers(false);
         }
     }
 
     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,12 +76,12 @@ 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;
@@ -105,11 +89,11 @@ public class Resolver {
     }
 
     public static void clearCache() {
-        final AbstractDNSClient client = ResolverApi.INSTANCE.getClient();
-        final DNSCache dnsCache = client.getCache();
-        if (dnsCache instanceof LRUCache) {
+        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();
+            ((LruCache) dnsCache).clear();
         }
     }
 
@@ -151,7 +135,7 @@ public class Resolver {
             }
         });
         threads[2] = new Thread(() -> {
-            List<Result> list = resolveNoSrvRecords(DNSName.from(domain), true);
+            List<Result> list = resolveNoSrvRecords(DnsName.from(domain), true);
             synchronized (fallbackResults) {
                 fallbackResults.addAll(list);
             }
@@ -193,6 +177,7 @@ public class Resolver {
             Result result = new Result();
             result.ip = InetAddress.getByName(domain);
             result.port = DEFAULT_PORT_XMPP;
+            result.authenticated = true;
             return Collections.singletonList(result);
         } catch (UnknownHostException e) {
             return Collections.emptyList();
@@ -200,7 +185,7 @@ public class Resolver {
     }
 
     private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
-        DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
+        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<>();
@@ -243,10 +228,10 @@ public class Resolver {
     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);
+            ResolverResult<D> results = resolveWithFallback(srv.name, type);
             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.authenticated = results.isAuthenticData() && authenticated;
                 resolverResult.ip = record.getInetAddress();
                 list.add(resolverResult);
             }
@@ -256,18 +241,29 @@ public class Resolver {
         return list;
     }
 
-    private static List<Result> resolveNoSrvRecords(DNSName dnsName, boolean withCnames) {
+    private static List<Result> resolveNoSrvRecords(DnsName dnsName, boolean withCnames) {
         List<Result> results = new ArrayList<>();
         try {
-            for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) {
-                results.add(Result.createDefault(dnsName, a.getInetAddress()));
+            ResolverResult<A> aResult = resolveWithFallback(dnsName, A.class);
+            Log.d("WUTr", "" + aResult.isAuthenticData() + " " + aResult.getAnswersOrEmptySet());
+            for (A a : aResult.getAnswersOrEmptySet()) {
+                Result r = Result.createDefault(dnsName, a.getInetAddress());
+                r.authenticated = aResult.isAuthenticData();
+                results.add(r);
             }
-            for (AAAA aaaa : resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) {
-                results.add(Result.createDefault(dnsName, aaaa.getInetAddress()));
+            ResolverResult<AAAA> aaaaResult = resolveWithFallback(dnsName, AAAA.class);
+            for (AAAA aaaa : aaaaResult.getAnswersOrEmptySet()) {
+                Result r = Result.createDefault(dnsName, aaaa.getInetAddress());
+                r.authenticated = aaaaResult.isAuthenticData();
+                results.add(r);
             }
             if (results.size() == 0 && withCnames) {
-                for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
-                    results.addAll(resolveNoSrvRecords(cname.name, false));
+                ResolverResult<CNAME> cnameResult = resolveWithFallback(dnsName, CNAME.class);
+                for (CNAME cname : cnameResult.getAnswersOrEmptySet()) {
+                    for (Result r : resolveNoSrvRecords(cname.name, false)) {
+                        r.authenticated = r.authenticated && cnameResult.isAuthenticData();
+                        results.add(r);
+                    }
                 }
             }
         } catch (final Throwable throwable) {
@@ -279,21 +275,10 @@ public class Resolver {
         return results;
     }
 
-    private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type) throws IOException {
-        return resolveWithFallback(dnsName, type, validateHostname());
-    }
-
-    private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type, boolean validateHostname) throws IOException {
+    private static <D extends Data> ResolverResult<D> resolveWithFallback(DnsName dnsName, Class<D> type) 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);
+            return DnssecResolverApi.INSTANCE.resolve(question);
         } catch (IOException e) {
             throw e;
         } catch (Throwable throwable) {
@@ -302,10 +287,6 @@ public class Resolver {
         return ResolverApi.INSTANCE.resolve(question);
     }
 
-    private static boolean validateHostname() {
-        return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname);
-    }
-
     public static class Result implements Comparable<Result> {
         public static final String DOMAIN = "domain";
         public static final String IP = "ip";
@@ -315,7 +296,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 +311,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,7 +319,7 @@ public class Resolver {
             return result;
         }
 
-        static Result createDefault(DNSName hostname) {
+        static Result createDefault(DnsName hostname) {
             return createDefault(hostname, null);
         }
 
@@ -350,7 +331,7 @@ public class Resolver {
                 result.ip = null;
             }
             final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME));
-            result.hostname = hostname == null ? null : DNSName.from(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;
@@ -392,7 +373,7 @@ public class Resolver {
             return port;
         }
 
-        public DNSName getHostname() {
+        public DnsName getHostname() {
             return hostname;
         }
 
@@ -482,7 +463,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 +480,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;
                     }

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java πŸ”—

@@ -225,6 +225,12 @@ public class XmppConnection implements Runnable {
         }
     }
 
+
+    public boolean resolverAuthenticated() {
+        if (currentResolverResult == null) return false;
+        return currentResolverResult.isAuthenticated();
+    }
+
     private void changeStatus(final Account.State nextStatus) {
         synchronized (this) {
             if (Thread.currentThread().isInterrupted()) {

src/main/res/layout/account_row.xml πŸ”—

@@ -43,12 +43,29 @@
                 android:singleLine="true"
                 android:textAppearance="@style/TextAppearance.Conversations.Subhead" />
 
-            <TextView
-                android:id="@+id/account_status"
-                android:layout_width="wrap_content"
+            <LinearLayout
+                android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/account_status_unknown"
-                android:textAppearance="@style/TextAppearance.Conversations.Body2" />
+                android:layout_centerVertical="true"
+                android:orientation="horizontal">
+
+               <ImageView
+                    android:id="@+id/verification_indicator"
+                    android:layout_width="?attr/TextSizeCaption"
+                    android:layout_height="?attr/TextSizeCaption"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginRight="4sp"
+                    android:alpha="0.70"
+                    android:gravity="center_vertical"
+                    android:src="@drawable/shield_question" />
+
+                <TextView
+                    android:id="@+id/account_status"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/account_status_unknown"
+                    android:textAppearance="@style/TextAppearance.Conversations.Body2" />
+            </LinearLayout>>
         </LinearLayout>
 
         <androidx.appcompat.widget.SwitchCompat

src/main/res/layout/activity_edit_account.xml πŸ”—

@@ -496,11 +496,46 @@
                         </TableLayout>
 
                         <RelativeLayout
-                            android:id="@+id/your_name_box"
+                            android:id="@+id/verification_box"
                             android:layout_width="wrap_content"
                             android:layout_height="match_parent"
                             android:layout_marginTop="12dp">
 
+                            <LinearLayout
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentLeft="true"
+                                android:layout_centerVertical="true"
+                                android:layout_toLeftOf="@+id/verification_indicator"
+                                android:orientation="vertical">
+
+                                <TextView
+                                    android:id="@+id/verification_message"
+                                    android:layout_width="wrap_content"
+                                    android:layout_height="wrap_content"
+                                    android:text="Not DNSSEC Verified"
+                                    android:textAppearance="@style/TextAppearance.Conversations.Body1.Tertiary" />
+                            </LinearLayout>
+
+                            <ImageView
+                                android:id="@+id/verification_indicator"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentRight="true"
+                                android:layout_centerVertical="true"
+                                android:alpha="?attr/icon_alpha"
+                                android:background="?attr/selectableItemBackgroundBorderless"
+                                android:padding="@dimen/image_button_padding"
+                                android:src="@drawable/shield_question"
+                                android:visibility="visible" />
+                        </RelativeLayout>
+
+                        <RelativeLayout
+                            android:id="@+id/your_name_box"
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:layout_marginTop="0dp">
+
                             <LinearLayout
                                 android:layout_width="wrap_content"
                                 android:layout_height="wrap_content"

src/main/res/xml/preferences.xml πŸ”—

@@ -308,11 +308,6 @@
                     android:key="dont_trust_system_cas"
                     android:summary="@string/pref_dont_trust_system_cas_summary"
                     android:title="@string/pref_dont_trust_system_cas_title" />
-                <CheckBoxPreference
-                    android:defaultValue="@bool/validate_hostname"
-                    android:key="validate_hostname"
-                    android:summary="@string/pref_validate_hostname_summary"
-                    android:title="@string/pref_validate_hostname" />
                 <CheckBoxPreference
                     android:defaultValue="@bool/allow_message_correction"
                     android:key="allow_message_correction"