Resolver.java

  1package eu.siacs.conversations.utils;
  2
  3import android.content.ContentValues;
  4import android.database.Cursor;
  5import android.util.Log;
  6import androidx.annotation.NonNull;
  7import com.google.common.base.MoreObjects;
  8import com.google.common.base.Objects;
  9import com.google.common.base.Strings;
 10import com.google.common.base.Throwables;
 11import com.google.common.collect.Collections2;
 12import com.google.common.collect.ImmutableList;
 13import com.google.common.collect.ImmutableMap;
 14import com.google.common.collect.Lists;
 15import com.google.common.collect.Ordering;
 16import com.google.common.net.InetAddresses;
 17import com.google.common.primitives.Ints;
 18import com.google.common.util.concurrent.Futures;
 19import com.google.common.util.concurrent.ListenableFuture;
 20import com.google.common.util.concurrent.MoreExecutors;
 21
 22import java.io.IOException;
 23
 24import de.gultsch.common.FutureMerger;
 25import eu.siacs.conversations.Config;
 26import eu.siacs.conversations.Conversations;
 27import eu.siacs.conversations.xmpp.Jid;
 28
 29import java.net.Inet4Address;
 30import java.net.InetAddress;
 31import java.net.UnknownHostException;
 32import java.util.ArrayList;
 33import java.util.Arrays;
 34import java.util.Collection;
 35import java.util.Collections;
 36import java.util.Comparator;
 37import java.util.List;
 38import java.util.Map;
 39import java.util.concurrent.ExecutionException;
 40import java.util.concurrent.ExecutorService;
 41import java.util.concurrent.Executors;
 42
 43import eu.siacs.conversations.Config;
 44import eu.siacs.conversations.Conversations;
 45import eu.siacs.conversations.R;
 46import eu.siacs.conversations.services.XmppConnectionService;
 47import eu.siacs.conversations.xmpp.Jid;
 48
 49import org.minidns.AbstractDnsClient;
 50import org.minidns.DnsCache;
 51import org.minidns.DnsClient;
 52import org.minidns.cache.LruCache;
 53import org.minidns.dnsmessage.Question;
 54import org.minidns.dnsname.DnsName;
 55import org.minidns.dnsname.InvalidDnsNameException;
 56import org.minidns.dnssec.DnssecResultNotAuthenticException;
 57import org.minidns.dnssec.DnssecValidationFailedException;
 58import org.minidns.dnsserverlookup.AndroidUsingExec;
 59import org.minidns.hla.DnssecResolverApi;
 60import org.minidns.hla.ResolverApi;
 61import org.minidns.hla.ResolverResult;
 62import org.minidns.iterative.ReliableDnsClient;
 63import org.minidns.record.A;
 64import org.minidns.record.AAAA;
 65import org.minidns.record.CNAME;
 66import org.minidns.record.Data;
 67import org.minidns.record.InternetAddressRR;
 68import org.minidns.record.Record;
 69import org.minidns.record.SRV;
 70
 71public class Resolver {
 72
 73    private static final Comparator<Result> RESULT_COMPARATOR =
 74            (left, right) -> {
 75                if (left.priority == right.priority) {
 76                    if (left.directTls == right.directTls) {
 77                        if (left.ip == null && right.ip == null) {
 78                            return 0;
 79                        } else if (left.ip != null && right.ip != null) {
 80                            if (left.ip instanceof Inet4Address
 81                                    && right.ip instanceof Inet4Address) {
 82                                return 0;
 83                            } else {
 84                                return left.ip instanceof Inet4Address ? -1 : 1;
 85                            }
 86                        } else {
 87                            return left.ip != null ? -1 : 1;
 88                        }
 89                    } else {
 90                        return left.directTls ? 1 : -1;
 91                    }
 92                } else {
 93                    return left.priority - right.priority;
 94                }
 95            };
 96
 97    private static final ExecutorService DNS_QUERY_EXECUTOR = Executors.newFixedThreadPool(12);
 98
 99    public static final int XMPP_PORT_STARTTLS = 5222;
100    private static final int XMPP_PORT_DIRECT_TLS = 5223;
101
102    private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
103    private static final String STARTTLS_SERVICE = "_xmpp-client";
104
105    private static XmppConnectionService SERVICE = null;
106
107    private static List<String> DNSSECLESS_TLDS = Arrays.asList(
108        "ae",
109        "aero",
110        "ai",
111        "al",
112        "ao",
113        "aq",
114        "as",
115        "ba",
116        "bb",
117        "bd",
118        "bf",
119        "bi",
120        "bj",
121        "bn",
122        "bo",
123        "bs",
124        "bw",
125        "cd",
126        "cf",
127        "cg",
128        "ci",
129        "ck",
130        "cm",
131        "cu",
132        "cv",
133        "cw",
134        "dj",
135        "dm",
136        "do",
137        "ec",
138        "eg",
139        "eh",
140        "er",
141        "et",
142        "fj",
143        "fk",
144        "ga",
145        "ge",
146        "gf",
147        "gh",
148        "gm",
149        "gp",
150        "gq",
151        "gt",
152        "gu",
153        "hm",
154        "ht",
155        "im",
156        "ir",
157        "je",
158        "jm",
159        "jo",
160        "ke",
161        "kh",
162        "km",
163        "kn",
164        "kp",
165        "kz",
166        "ls",
167        "mg",
168        "mh",
169        "mk",
170        "ml",
171        "mm",
172        "mo",
173        "mp",
174        "mq",
175        "ms",
176        "mt",
177        "mu",
178        "mv",
179        "mw",
180        "mz",
181        "ne",
182        "ng",
183        "ni",
184        "np",
185        "nr",
186        "om",
187        "pa",
188        "pf",
189        "pg",
190        "pk",
191        "pn",
192        "ps",
193        "py",
194        "qa",
195        "rw",
196        "sd",
197        "sl",
198        "sm",
199        "so",
200        "sr",
201        "sv",
202        "sy",
203        "sz",
204        "tc",
205        "td",
206        "tg",
207        "tj",
208        "to",
209        "tr",
210        "va",
211        "vg",
212        "vi",
213        "ye",
214        "zm",
215        "zw"
216    );
217
218    protected static final Map<String, String> knownSRV = ImmutableMap.of(
219        "_xmpp-client._tcp.yax.im", "xmpp.yaxim.org",
220        "_xmpps-client._tcp.yax.im", "xmpp.yaxim.org",
221        "_xmpp-server._tcp.yax.im", "xmpp.yaxim.org"
222    );
223
224    public static void init(XmppConnectionService service) {
225        Resolver.SERVICE = service;
226        DnsClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE);
227        DnsClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE);
228        DnsClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service));
229        DnsClient.addDnsServerLookupMechanism(new com.cheogram.android.DnsFallback());
230        final AbstractDnsClient client = ResolverApi.INSTANCE.getClient();
231        if (client instanceof ReliableDnsClient) {
232            ((ReliableDnsClient) client).setUseHardcodedDnsServers(false);
233        }
234        final AbstractDnsClient dnssecclient = DnssecResolverApi.INSTANCE.getClient();
235        if (dnssecclient instanceof ReliableDnsClient) {
236            ((ReliableDnsClient) dnssecclient).setUseHardcodedDnsServers(false);
237        }
238    }
239
240    public static List<Result> fromHardCoded(final String hostname, final int port) {
241        final Result result = new Result();
242        result.hostname = DnsName.from(hostname);
243        result.port = port;
244        result.directTls = useDirectTls(port);
245        result.authenticated = true;
246        return Collections.singletonList(result);
247    }
248
249    public static void checkDomain(final Jid jid) {
250        DnsName.from(jid.getDomain());
251    }
252
253    public static boolean invalidHostname(final String hostname) {
254        try {
255            DnsName.from(hostname);
256            return false;
257        } catch (final InvalidDnsNameException | IllegalArgumentException e) {
258            return true;
259        }
260    }
261
262    public static void clearCache() {
263        final AbstractDnsClient client = ResolverApi.INSTANCE.getClient();
264        final DnsCache dnsCache = client.getCache();
265        if (dnsCache instanceof LruCache) {
266            Log.d(Config.LOGTAG,"clearing DNS cache");
267            ((LruCache) dnsCache).clear();
268        }
269
270        final AbstractDnsClient clientSec = DnssecResolverApi.INSTANCE.getClient();
271        final DnsCache dnsCacheSec = clientSec.getCache();
272        if (dnsCacheSec instanceof LruCache) {
273            Log.d(Config.LOGTAG,"clearing DNSSEC cache");
274            ((LruCache) dnsCacheSec).clear();
275        }
276    }
277
278    public static boolean useDirectTls(final int port) {
279        return port == 443 || port == XMPP_PORT_DIRECT_TLS;
280    }
281
282    public static List<Result> resolve(final String domain) {
283        final List<Result> ipResults = fromIpAddress(domain);
284        if (!ipResults.isEmpty()) {
285            return ipResults;
286        }
287
288        final var startTls = resolveSrvAsFuture(domain, false);
289        final var directTls = resolveSrvAsFuture(domain, true);
290
291        final var combined = FutureMerger.successfulAsList(ImmutableList.of(startTls, directTls));
292
293        final var combinedWithFallback =
294                Futures.transformAsync(
295                        combined,
296                        results -> {
297                            if (results.isEmpty()) {
298                                return resolveNoSrvAsFuture(DnsName.from(domain), true);
299                            } else {
300                                return Futures.immediateFuture(results);
301                            }
302                        },
303                        MoreExecutors.directExecutor());
304        final var orderedFuture =
305                Futures.transform(
306                        combinedWithFallback,
307                        all -> Ordering.from(RESULT_COMPARATOR).immutableSortedCopy(all),
308                        MoreExecutors.directExecutor());
309        try {
310            final var ordered = orderedFuture.get();
311            Log.d(Config.LOGTAG, "Resolver (" + ordered.size() + "): " + ordered);
312            return ordered;
313        } catch (final ExecutionException e) {
314            Log.d(Config.LOGTAG, "error resolving DNS", e);
315            return Collections.emptyList();
316        } catch (final InterruptedException e) {
317            Log.d(Config.LOGTAG, "DNS resolution interrupted");
318            return Collections.emptyList();
319        }
320    }
321
322    private static List<Result> fromIpAddress(final String domain) {
323        if (IP.matches(domain)) {
324            final InetAddress inetAddress;
325            try {
326                inetAddress = InetAddresses.forString(domain);
327            } catch (final IllegalArgumentException e) {
328                return Collections.emptyList();
329            }
330            return Result.createWithDefaultPorts(null, inetAddress);
331        } else {
332            return Collections.emptyList();
333        }
334    }
335
336    private static ListenableFuture<List<Result>> resolveSrvAsFuture(
337            final String domain, final boolean directTls) {
338        final DnsName dnsName =
339                DnsName.from(
340                        (directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
341        final var resultFuture = resolveAsFuture(dnsName, SRV.class);
342        return Futures.transformAsync(
343                resultFuture,
344                result -> resolveIpsAsFuture(result, directTls),
345                MoreExecutors.directExecutor());
346    }
347
348    @NonNull
349    private static ListenableFuture<List<Result>> resolveIpsAsFuture(
350            final ResolverResult<SRV> srvResolverResult, final boolean directTls) {
351        final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
352                new ImmutableList.Builder<>();
353        for (final SRV record : srvResolverResult.getAnswersOrEmptySet()) {
354            if (record.target.length() == 0 && record.priority == 0) {
355                continue;
356            }
357            final var ipv4sRaw =
358                    resolveIpsAsFuture(
359                            record, A.class, srvResolverResult.isAuthenticData(), directTls);
360            final var ipv4s =
361                    Futures.transform(
362                            ipv4sRaw,
363                            results -> {
364                                if (results.isEmpty()) {
365                                    final Result resolverResult =
366                                            Result.fromRecord(record, directTls);
367                                    resolverResult.authenticated =
368                                            srvResolverResult.isAuthenticData();
369                                    return Collections.singletonList(resolverResult);
370                                } else {
371                                    return results;
372                                }
373                            },
374                            MoreExecutors.directExecutor());
375            final var ipv6s =
376                    resolveIpsAsFuture(
377                            record, AAAA.class, srvResolverResult.isAuthenticData(), directTls);
378            futuresBuilder.add(ipv4s);
379            futuresBuilder.add(ipv6s);
380        }
381        final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
382        return FutureMerger.successfulAsList(futures);
383    }
384
385    private static ListenableFuture<List<Result>> merge(
386            final Collection<ListenableFuture<List<Result>>> futures) {
387        return Futures.transform(
388                Futures.successfulAsList(futures),
389                lists -> {
390                    final var builder = new ImmutableList.Builder<Result>();
391                    for (final Collection<Result> list : lists) {
392                        if (list == null) {
393                            continue;
394                        }
395                        builder.addAll(list);
396                    }
397                    return builder.build();
398                },
399                MoreExecutors.directExecutor());
400    }
401
402    private static <D extends InternetAddressRR<?>>
403            ListenableFuture<List<Result>> resolveIpsAsFuture(
404                    final SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
405        final var resultFuture = resolveAsFuture(srv.target, type);
406        return Futures.transform(
407                resultFuture,
408                result -> {
409                    final var builder = new ImmutableList.Builder<Result>();
410                    for (D record : result.getAnswersOrEmptySet()) {
411                        Result resolverResult = Result.fromRecord(srv, directTls);
412                        resolverResult.authenticated =
413                                result.isAuthenticData()
414                                        && authenticated; // TODO technically it does not matter if
415                        // the IP
416                        // was authenticated
417                        resolverResult.ip = record.getInetAddress();
418                        builder.add(resolverResult);
419                    }
420                    return builder.build();
421                },
422                MoreExecutors.directExecutor());
423    }
424
425    private static ListenableFuture<List<Result>> resolveNoSrvAsFuture(
426            final DnsName dnsName, boolean cName) {
427        final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
428                new ImmutableList.Builder<>();
429        ListenableFuture<List<Result>> aRecordResults =
430                Futures.transform(
431                        resolveAsFuture(dnsName, A.class),
432                        result ->
433                                Lists.transform(
434                                        ImmutableList.copyOf(result.getAnswersOrEmptySet()),
435                                        a -> Result.createDefault(dnsName, a.getInetAddress(), result.isAuthenticData())),
436                        MoreExecutors.directExecutor());
437        futuresBuilder.add(aRecordResults);
438        ListenableFuture<List<Result>> aaaaRecordResults =
439                Futures.transform(
440                        resolveAsFuture(dnsName, AAAA.class),
441                        result ->
442                                Lists.transform(
443                                        ImmutableList.copyOf(result.getAnswersOrEmptySet()),
444                                        aaaa ->
445                                                Result.createDefault(
446                                                        dnsName, aaaa.getInetAddress(), result.isAuthenticData())),
447                        MoreExecutors.directExecutor());
448        futuresBuilder.add(aaaaRecordResults);
449        if (cName) {
450            ListenableFuture<List<Result>> cNameRecordResults =
451                    Futures.transformAsync(
452                            resolveAsFuture(dnsName, CNAME.class),
453                            result -> {
454                                Collection<ListenableFuture<List<Result>>> test =
455                                        Lists.transform(
456                                                ImmutableList.copyOf(result.getAnswersOrEmptySet()),
457                                                cname -> resolveNoSrvAsFuture(cname.target, false));
458                                return FutureMerger.successfulAsList(test);
459                            },
460                            MoreExecutors.directExecutor());
461            futuresBuilder.add(cNameRecordResults);
462        }
463        final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
464        final var noSrvFallbacks = FutureMerger.successfulAsList(futures);
465        return Futures.transform(
466                noSrvFallbacks,
467                results -> {
468                    if (results.isEmpty()) {
469                        return Result.createDefaults(dnsName);
470                    } else {
471                        return results;
472                    }
473                },
474                MoreExecutors.directExecutor());
475    }
476
477    private static <D extends Data> ListenableFuture<ResolverResult<D>> resolveAsFuture(
478            final DnsName dnsName, final Class<D> type) {
479        final var start = System.currentTimeMillis();
480        return Futures.submit(
481                () -> {
482                    final Question question = new Question(dnsName, Record.TYPE.getType(type));
483                    if (!DNSSECLESS_TLDS.contains(dnsName.getLabels()[0].toString())) {
484                        for (int i = 0; i < 5; i++) {
485                            if (System.currentTimeMillis() - start > 5000) {
486                                Log.d(Config.LOGTAG, "DNS taking too long, abort DNSSEC retries after " + i + " for " + type.getSimpleName() + " " + dnsName);
487                                break;
488                            }
489                            Log.d(Config.LOGTAG, "DNSSEC try " + i + " for " + type.getSimpleName() + " " + dnsName);
490                            try {
491                                ResolverResult<D> result = DnssecResolverApi.INSTANCE.resolve(question);
492                                if (result.wasSuccessful() && !result.isAuthenticData()) {
493                                    Log.d(Config.LOGTAG, "DNSSEC validation failed for " + type.getSimpleName() + " : " + result.getUnverifiedReasons());
494                                }
495                                return result;
496                            } catch (DnssecValidationFailedException e) {
497                                Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + dnsName + " with DNSSEC. Try: " + i, e);
498                                // Try again, may be transient DNSSEC failure https://github.com/MiniDNS/minidns/issues/132
499                                if ("CNAME".equals(type.getSimpleName())) break; // CNAME failure on NXDOMAIN is common don't retry?
500                            } catch (Throwable throwable) {
501                                Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable);
502                                break;
503                            }
504                        }
505                    }
506                    return ResolverApi.INSTANCE.resolve(question);
507                },
508                DNS_QUERY_EXECUTOR);
509    }
510
511    public static class Result {
512        public static final String DOMAIN = "domain";
513        public static final String IP = "ip";
514        public static final String HOSTNAME = "hostname";
515        public static final String PORT = "port";
516        public static final String PRIORITY = "priority";
517        public static final String DIRECT_TLS = "directTls";
518        public static final String AUTHENTICATED = "authenticated";
519        private InetAddress ip;
520        private DnsName hostname;
521        private int port = XMPP_PORT_STARTTLS;
522        private boolean directTls = false;
523        private boolean authenticated = false;
524        private int priority;
525
526        static Result fromRecord(final SRV srv, final boolean directTls) {
527            final Result result = new Result();
528            result.port = srv.port;
529            result.hostname = srv.target;
530            result.directTls = directTls;
531            result.priority = srv.priority;
532            return result;
533        }
534
535        static List<Result> createWithDefaultPorts(final DnsName hostname, final InetAddress ip) {
536            return Lists.transform(
537                    Arrays.asList(XMPP_PORT_STARTTLS),
538                    p -> createDefault(hostname, ip, p, false));
539        }
540
541        static Result createDefault(final DnsName hostname, final InetAddress ip, final int port, final boolean authenticated) {
542            Result result = new Result();
543            result.port = port;
544            result.hostname = hostname;
545            result.ip = ip;
546            result.authenticated = authenticated;
547            return result;
548        }
549
550        static Result createDefault(final DnsName hostname, final InetAddress ip, final boolean authenticated) {
551            return createDefault(hostname, ip, XMPP_PORT_STARTTLS, authenticated);
552        }
553
554        static Result createDefault(final DnsName hostname) {
555            return createDefault(hostname, null, XMPP_PORT_STARTTLS, false);
556        }
557
558        static List<Result> createDefaults(
559                final DnsName hostname, final Collection<InetAddress> inetAddresses) {
560            final ImmutableList.Builder<Result> builder = new ImmutableList.Builder<>();
561            for (final InetAddress inetAddress : inetAddresses) {
562                builder.addAll(createWithDefaultPorts(hostname, inetAddress));
563            }
564            return builder.build();
565        }
566
567        static List<Result> createDefaults(final DnsName hostname) {
568            return createWithDefaultPorts(hostname, null);
569        }
570
571        public static Result fromCursor(final Cursor cursor) {
572            final Result result = new Result();
573            try {
574                result.ip =
575                        InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndexOrThrow(IP)));
576            } catch (final UnknownHostException e) {
577                result.ip = null;
578            }
579            final String hostname = cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME));
580            result.hostname = hostname == null ? null : DnsName.from(hostname);
581            result.port = cursor.getInt(cursor.getColumnIndexOrThrow(PORT));
582            result.priority = cursor.getInt(cursor.getColumnIndexOrThrow(PRIORITY));
583            result.authenticated = cursor.getInt(cursor.getColumnIndexOrThrow(AUTHENTICATED)) > 0;
584            result.directTls = cursor.getInt(cursor.getColumnIndexOrThrow(DIRECT_TLS)) > 0;
585            return result;
586        }
587
588        @Override
589        public boolean equals(Object o) {
590            if (this == o) return true;
591            if (o == null || getClass() != o.getClass()) return false;
592            Result result = (Result) o;
593            return port == result.port
594                    && directTls == result.directTls
595                    && authenticated == result.authenticated
596                    && priority == result.priority
597                    && Objects.equal(ip, result.ip)
598                    && Objects.equal(hostname, result.hostname);
599        }
600
601        @Override
602        public int hashCode() {
603            return Objects.hashCode(ip, hostname, port, directTls, authenticated, priority);
604        }
605
606        public InetAddress getIp() {
607            return ip;
608        }
609
610        public int getPort() {
611            return port;
612        }
613
614        public DnsName getHostname() {
615            return hostname;
616        }
617
618        public boolean isDirectTls() {
619            return directTls;
620        }
621
622        public boolean isAuthenticated() {
623            return authenticated;
624        }
625
626        @Override
627        @NonNull
628        public String toString() {
629            return MoreObjects.toStringHelper(this)
630                    .add("ip", ip)
631                    .add("hostname", hostname)
632                    .add("port", port)
633                    .add("directTls", directTls)
634                    .add("authenticated", authenticated)
635                    .add("priority", priority)
636                    .toString();
637        }
638
639        public String asDestination() {
640            return ip != null ? InetAddresses.toAddrString(ip) : hostname.toString();
641        }
642
643        public ContentValues toContentValues() {
644            final ContentValues contentValues = new ContentValues();
645            contentValues.put(IP, ip == null ? null : ip.getAddress());
646            contentValues.put(HOSTNAME, hostname == null ? null : hostname.toString());
647            contentValues.put(PORT, port);
648            contentValues.put(PRIORITY, priority);
649            contentValues.put(DIRECT_TLS, directTls ? 1 : 0);
650            contentValues.put(AUTHENTICATED, authenticated ? 1 : 0);
651            return contentValues;
652        }
653
654        public Result seeOtherHost(final String seeOtherHost) {
655            final String hostname = seeOtherHost.trim();
656            if (hostname.isEmpty()) {
657                return null;
658            }
659            final Result result = new Result();
660            result.directTls = this.directTls;
661            final int portSegmentStart = hostname.lastIndexOf(':');
662            if (hostname.charAt(hostname.length() - 1) != ']'
663                    && portSegmentStart >= 0
664                    && hostname.length() >= portSegmentStart + 1) {
665                final String hostPart = hostname.substring(0, portSegmentStart);
666                final String portPart = hostname.substring(portSegmentStart + 1);
667                final Integer port = Ints.tryParse(portPart);
668                if (port == null || Strings.isNullOrEmpty(hostPart)) {
669                    return null;
670                }
671                final String host = eu.siacs.conversations.utils.IP.unwrapIPv6(hostPart);
672                result.port = port;
673                if (InetAddresses.isInetAddress(host)) {
674                    final InetAddress inetAddress;
675                    try {
676                        inetAddress = InetAddresses.forString(host);
677                    } catch (final IllegalArgumentException e) {
678                        return null;
679                    }
680                    result.ip = inetAddress;
681                } else {
682                    if (hostPart.trim().isEmpty()) {
683                        return null;
684                    }
685                    try {
686                        result.hostname = DnsName.from(hostPart.trim());
687                    } catch (final Exception e) {
688                        return null;
689                    }
690                }
691            } else {
692                final String host = eu.siacs.conversations.utils.IP.unwrapIPv6(hostname);
693                if (InetAddresses.isInetAddress(host)) {
694                    final InetAddress inetAddress;
695                    try {
696                        inetAddress = InetAddresses.forString(host);
697                    } catch (final IllegalArgumentException e) {
698                        return null;
699                    }
700                    result.ip = inetAddress;
701                } else {
702                    try {
703                        result.hostname = DnsName.from(hostname);
704                    } catch (final Exception e) {
705                        return null;
706                    }
707                }
708                result.port = port;
709            }
710            return result;
711        }
712    }
713}