Resolver.java

  1package eu.siacs.conversations.utils;
  2
  3import android.content.Context;
  4import android.support.annotation.NonNull;
  5import android.util.Log;
  6
  7import java.io.IOException;
  8import java.net.Inet4Address;
  9import java.net.InetAddress;
 10import java.util.ArrayList;
 11import java.util.Collections;
 12import java.util.List;
 13
 14import de.measite.minidns.DNSClient;
 15import de.measite.minidns.DNSName;
 16import de.measite.minidns.dnssec.DNSSECResultNotAuthenticException;
 17import de.measite.minidns.hla.DnssecResolverApi;
 18import de.measite.minidns.hla.ResolverApi;
 19import de.measite.minidns.hla.ResolverResult;
 20import de.measite.minidns.record.A;
 21import de.measite.minidns.record.AAAA;
 22import de.measite.minidns.record.CNAME;
 23import de.measite.minidns.record.Data;
 24import de.measite.minidns.record.InternetAddressRR;
 25import de.measite.minidns.record.SRV;
 26import eu.siacs.conversations.Config;
 27import eu.siacs.conversations.R;
 28import eu.siacs.conversations.services.XmppConnectionService;
 29
 30public class Resolver {
 31
 32    private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
 33    private static final String STARTTLS_SERICE = "_xmpp-client";
 34
 35    private static XmppConnectionService SERVICE = null;
 36
 37
 38    public static void registerXmppConnectionService(XmppConnectionService service) {
 39        Resolver.SERVICE = service;
 40        registerLookupMechanism(service);
 41    }
 42
 43    private static void registerLookupMechanism(Context context) {
 44        DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(context));
 45    }
 46
 47    public static List<Result> resolve(String domain) {
 48        List<Result> results = new ArrayList<>();
 49        try {
 50            results.addAll(resolveSrv(domain,true));
 51        } catch (Throwable throwable) {
 52            Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": error resolving SRV record (direct TLS)",throwable);
 53        }
 54        try {
 55            results.addAll(resolveSrv(domain,false));
 56        } catch (Throwable throwable) {
 57            Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": error resolving SRV record (STARTTLS)",throwable);
 58        }
 59        if (results.size() == 0) {
 60            results.addAll(resolveNoSrvRecords(DNSName.from(domain),true));
 61        }
 62        Collections.sort(results);
 63        Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": "+results.toString());
 64        return results;
 65    }
 66
 67    private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
 68        if (Thread.currentThread().isInterrupted()) {
 69            return Collections.emptyList();
 70        }
 71        DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE)+"._tcp."+domain);
 72        ResolverResult<SRV> result = resolveWithFallback(dnsName,SRV.class);
 73        List<Result> results = new ArrayList<>();
 74        for(SRV record : result.getAnswersOrEmptySet()) {
 75            final boolean addedIPv4 = results.addAll(resolveIp(record,A.class,result.isAuthenticData(),directTls));
 76            results.addAll(resolveIp(record,AAAA.class,result.isAuthenticData(),directTls));
 77            if (!addedIPv4 && !Thread.currentThread().isInterrupted()) {
 78                Result resolverResult = Result.fromRecord(record, directTls);
 79                resolverResult.authenticated = resolverResult.isAuthenticated();
 80                results.add(resolverResult);
 81            }
 82        }
 83        return results;
 84    }
 85
 86    private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
 87        if (Thread.currentThread().isInterrupted()) {
 88            return Collections.emptyList();
 89        }
 90        List<Result> list = new ArrayList<>();
 91        try {
 92            //TODO fix the DNSName.from(srv.name.toString() workaround once minidns 0.2.2 is out
 93            ResolverResult<D> results = resolveWithFallback(DNSName.from(srv.name.toString()),type, authenticated);
 94            for (D record : results.getAnswersOrEmptySet()) {
 95                Result resolverResult = Result.fromRecord(srv, directTls);
 96                resolverResult.authenticated = results.isAuthenticData() && authenticated;
 97                resolverResult.ip = record.getInetAddress();
 98                list.add(resolverResult);
 99            }
100        } catch (Throwable t) {
101            Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": error resolving "+type.getSimpleName()+" "+t.getMessage());
102        }
103        return list;
104    }
105
106    private static List<Result> resolveNoSrvRecords(DNSName dnsName, boolean withCnames) {
107        List<Result> results = new ArrayList<>();
108        try {
109            for(A a : resolveWithFallback(dnsName,A.class,false).getAnswersOrEmptySet()) {
110                results.add(Result.createDefault(dnsName,a.getInetAddress()));
111            }
112            for(AAAA aaaa : resolveWithFallback(dnsName,AAAA.class,false).getAnswersOrEmptySet()) {
113                results.add(Result.createDefault(dnsName,aaaa.getInetAddress()));
114            }
115            if (results.size() == 0 && withCnames) {
116                for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
117                    results.addAll(resolveNoSrvRecords(cname.name, false));
118                }
119            }
120        } catch (Throwable throwable) {
121            Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records",throwable);
122        }
123        results.add(Result.createDefault(dnsName));
124        return results;
125    }
126
127    private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type) throws IOException {
128        return resolveWithFallback(dnsName,type,validateHostname());
129    }
130
131    private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type, boolean validateHostname) throws IOException {
132        if (!validateHostname) {
133            return ResolverApi.INSTANCE.resolve(dnsName, type);
134        }
135        try {
136            final ResolverResult<D> r = DnssecResolverApi.INSTANCE.resolveDnssecReliable(dnsName, type);
137            if (r.wasSuccessful()) {
138                if (r.getAnswers().isEmpty() && type.equals(SRV.class)) {
139                    Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": resolving  SRV records of " + dnsName.toString() + " with DNSSEC yielded empty result");
140                }
141                return r;
142            }
143            Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", r.getResolutionUnsuccessfulException());
144        } catch (DNSSECResultNotAuthenticException e) {
145            Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", e);
146        } catch (IOException e) {
147            throw e;
148        } catch (Throwable throwable) {
149            Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable);
150        }
151        return ResolverApi.INSTANCE.resolve(dnsName, type);
152    }
153
154    private static boolean validateHostname() {
155        return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname);
156    }
157
158    public static class Result implements Comparable<Result> {
159        private InetAddress ip;
160        private DNSName hostname;
161        private int port = 5222;
162        private boolean directTls = false;
163        private boolean authenticated =false;
164        private int priority;
165
166        public InetAddress getIp() {
167            return ip;
168        }
169
170        public int getPort() {
171            return port;
172        }
173
174        public DNSName getHostname() {
175            return hostname;
176        }
177
178        public boolean isDirectTls() {
179            return directTls;
180        }
181
182        public boolean isAuthenticated() {
183            return authenticated;
184        }
185
186        @Override
187        public String toString() {
188            return "Result{" +
189                    "ip='" + (ip==null?null:ip.getHostAddress()) + '\'' +
190                    ", hostame='" + hostname.toString() + '\'' +
191                    ", port=" + port +
192                    ", directTls=" + directTls +
193                    ", authenticated=" + authenticated +
194                    ", priority=" + priority +
195                    '}';
196        }
197
198        @Override
199        public int compareTo(@NonNull Result result) {
200            if (result.priority == priority) {
201                if (directTls == result.directTls) {
202                    if (ip == null && result.ip == null) {
203                        return 0;
204                    } else if (ip != null && result.ip != null) {
205                        if (ip instanceof Inet4Address && result.ip instanceof Inet4Address) {
206                            return 0;
207                        } else {
208                            return ip instanceof Inet4Address ? -1 : 1;
209                        }
210                    } else {
211                        return ip != null ? -1 : 1;
212                    }
213                } else {
214                    return directTls ? -1 : 1;
215                }
216            } else {
217                return priority - result.priority;
218            }
219        }
220
221        public static Result fromRecord(SRV srv, boolean directTls) {
222            Result result = new Result();
223            result.port = srv.port;
224            result.hostname = srv.name;
225            result.directTls = directTls;
226            result.priority = srv.priority;
227            return result;
228        }
229
230        public static Result createDefault(DNSName hostname, InetAddress ip) {
231            Result result = new Result();
232            result.port = 5222;
233            result.hostname = hostname;
234            result.ip = ip;
235            return result;
236        }
237
238        public static Result createDefault(DNSName hostname) {
239            return createDefault(hostname,null);
240        }
241    }
242
243}