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            // If your DNS server sucks, just don't do DNSSEC
238            ((ReliableDnsClient) dnssecclient).setMode(ReliableDnsClient.Mode.recursiveOnly);
239        }
240    }
241
242    public static List<Result> fromHardCoded(final String hostname, final int port) {
243        final Result result = new Result();
244        result.hostname = DnsName.from(hostname);
245        result.port = port;
246        result.directTls = useDirectTls(port);
247        result.authenticated = true;
248        return Collections.singletonList(result);
249    }
250
251    public static void checkDomain(final Jid jid) {
252        DnsName.from(jid.getDomain());
253    }
254
255    public static boolean invalidHostname(final String hostname) {
256        try {
257            DnsName.from(hostname);
258            return false;
259        } catch (final InvalidDnsNameException | IllegalArgumentException e) {
260            return true;
261        }
262    }
263
264    public static void clearCache() {
265        final AbstractDnsClient client = ResolverApi.INSTANCE.getClient();
266        final DnsCache dnsCache = client.getCache();
267        if (dnsCache instanceof LruCache) {
268            Log.d(Config.LOGTAG,"clearing DNS cache");
269            ((LruCache) dnsCache).clear();
270        }
271
272        final AbstractDnsClient clientSec = DnssecResolverApi.INSTANCE.getClient();
273        final DnsCache dnsCacheSec = clientSec.getCache();
274        if (dnsCacheSec instanceof LruCache) {
275            Log.d(Config.LOGTAG,"clearing DNSSEC cache");
276            ((LruCache) dnsCacheSec).clear();
277        }
278    }
279
280    public static boolean useDirectTls(final int port) {
281        return port == 443 || port == XMPP_PORT_DIRECT_TLS;
282    }
283
284    public static List<Result> resolve(final String domain) {
285        final List<Result> ipResults = fromIpAddress(domain);
286        if (!ipResults.isEmpty()) {
287            return ipResults;
288        }
289
290        final var startTls = resolveSrvAsFuture(domain, false);
291        final var directTls = resolveSrvAsFuture(domain, true);
292
293        final var combined = FutureMerger.successfulAsList(ImmutableList.of(startTls, directTls));
294
295        final var combinedWithFallback =
296                Futures.transformAsync(
297                        combined,
298                        results -> {
299                            if (results.isEmpty()) {
300                                return resolveNoSrvAsFuture(DnsName.from(domain), true);
301                            } else {
302                                return Futures.immediateFuture(results);
303                            }
304                        },
305                        MoreExecutors.directExecutor());
306        final var orderedFuture =
307                Futures.transform(
308                        combinedWithFallback,
309                        all -> Ordering.from(RESULT_COMPARATOR).immutableSortedCopy(all),
310                        MoreExecutors.directExecutor());
311        try {
312            final var ordered = orderedFuture.get();
313            Log.d(Config.LOGTAG, "Resolver (" + ordered.size() + "): " + ordered);
314            return ordered;
315        } catch (final ExecutionException e) {
316            Log.d(Config.LOGTAG, "error resolving DNS", e);
317            return Collections.emptyList();
318        } catch (final InterruptedException e) {
319            Log.d(Config.LOGTAG, "DNS resolution interrupted");
320            return Collections.emptyList();
321        }
322    }
323
324    private static List<Result> fromIpAddress(final String domain) {
325        if (IP.matches(domain)) {
326            final InetAddress inetAddress;
327            try {
328                inetAddress = InetAddresses.forString(domain);
329            } catch (final IllegalArgumentException e) {
330                return Collections.emptyList();
331            }
332            return Result.createWithDefaultPorts(null, inetAddress);
333        } else {
334            return Collections.emptyList();
335        }
336    }
337
338    private static ListenableFuture<List<Result>> resolveSrvAsFuture(
339            final String domain, final boolean directTls) {
340        final DnsName dnsName =
341                DnsName.from(
342                        (directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
343        final var resultFuture = resolveAsFuture(dnsName, SRV.class);
344        return Futures.transformAsync(
345                resultFuture,
346                result -> resolveIpsAsFuture(result, directTls),
347                MoreExecutors.directExecutor());
348    }
349
350    @NonNull
351    private static ListenableFuture<List<Result>> resolveIpsAsFuture(
352            final ResolverResult<SRV> srvResolverResult, final boolean directTls) {
353        final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
354                new ImmutableList.Builder<>();
355        for (final SRV record : srvResolverResult.getAnswersOrEmptySet()) {
356            if (record.target.length() == 0 && record.priority == 0) {
357                continue;
358            }
359            final var ipv4sRaw =
360                    resolveIpsAsFuture(
361                            record, A.class, srvResolverResult.isAuthenticData(), directTls);
362            final var ipv4s =
363                    Futures.transform(
364                            ipv4sRaw,
365                            results -> {
366                                if (results.isEmpty()) {
367                                    final Result resolverResult =
368                                            Result.fromRecord(record, directTls);
369                                    resolverResult.authenticated =
370                                            srvResolverResult.isAuthenticData();
371                                    return Collections.singletonList(resolverResult);
372                                } else {
373                                    return results;
374                                }
375                            },
376                            MoreExecutors.directExecutor());
377            final var ipv6s =
378                    resolveIpsAsFuture(
379                            record, AAAA.class, srvResolverResult.isAuthenticData(), directTls);
380            futuresBuilder.add(ipv4s);
381            futuresBuilder.add(ipv6s);
382        }
383        final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
384        return FutureMerger.successfulAsList(futures);
385    }
386
387    private static ListenableFuture<List<Result>> merge(
388            final Collection<ListenableFuture<List<Result>>> futures) {
389        return Futures.transform(
390                Futures.successfulAsList(futures),
391                lists -> {
392                    final var builder = new ImmutableList.Builder<Result>();
393                    for (final Collection<Result> list : lists) {
394                        if (list == null) {
395                            continue;
396                        }
397                        builder.addAll(list);
398                    }
399                    return builder.build();
400                },
401                MoreExecutors.directExecutor());
402    }
403
404    private static <D extends InternetAddressRR<?>>
405            ListenableFuture<List<Result>> resolveIpsAsFuture(
406                    final SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
407        final var resultFuture = resolveAsFuture(srv.target, type);
408        return Futures.transform(
409                resultFuture,
410                result -> {
411                    final var builder = new ImmutableList.Builder<Result>();
412                    for (D record : result.getAnswersOrEmptySet()) {
413                        Result resolverResult = Result.fromRecord(srv, directTls);
414                        resolverResult.authenticated =
415                                result.isAuthenticData()
416                                        && authenticated; // TODO technically it does not matter if
417                        // the IP
418                        // was authenticated
419                        resolverResult.ip = record.getInetAddress();
420                        builder.add(resolverResult);
421                    }
422                    return builder.build();
423                },
424                MoreExecutors.directExecutor());
425    }
426
427    private static ListenableFuture<List<Result>> resolveNoSrvAsFuture(
428            final DnsName dnsName, boolean cName) {
429        final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
430                new ImmutableList.Builder<>();
431        ListenableFuture<List<Result>> aRecordResults =
432                Futures.transform(
433                        resolveAsFuture(dnsName, A.class),
434                        result ->
435                                Lists.transform(
436                                        ImmutableList.copyOf(result.getAnswersOrEmptySet()),
437                                        a -> Result.createDefault(dnsName, a.getInetAddress(), result.isAuthenticData())),
438                        MoreExecutors.directExecutor());
439        futuresBuilder.add(aRecordResults);
440        ListenableFuture<List<Result>> aaaaRecordResults =
441                Futures.transform(
442                        resolveAsFuture(dnsName, AAAA.class),
443                        result ->
444                                Lists.transform(
445                                        ImmutableList.copyOf(result.getAnswersOrEmptySet()),
446                                        aaaa ->
447                                                Result.createDefault(
448                                                        dnsName, aaaa.getInetAddress(), result.isAuthenticData())),
449                        MoreExecutors.directExecutor());
450        futuresBuilder.add(aaaaRecordResults);
451        if (cName) {
452            ListenableFuture<List<Result>> cNameRecordResults =
453                    Futures.transformAsync(
454                            resolveAsFuture(dnsName, CNAME.class),
455                            result -> {
456                                Collection<ListenableFuture<List<Result>>> test =
457                                        Lists.transform(
458                                                ImmutableList.copyOf(result.getAnswersOrEmptySet()),
459                                                cname -> resolveNoSrvAsFuture(cname.target, false));
460                                return FutureMerger.successfulAsList(test);
461                            },
462                            MoreExecutors.directExecutor());
463            futuresBuilder.add(cNameRecordResults);
464        }
465        final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
466        final var noSrvFallbacks = FutureMerger.successfulAsList(futures);
467        return Futures.transform(
468                noSrvFallbacks,
469                results -> {
470                    if (results.isEmpty()) {
471                        return Result.createDefaults(dnsName);
472                    } else {
473                        return results;
474                    }
475                },
476                MoreExecutors.directExecutor());
477    }
478
479    private static <D extends Data> ListenableFuture<ResolverResult<D>> resolveAsFuture(
480            final DnsName dnsName, final Class<D> type) {
481        final var start = System.currentTimeMillis();
482        return Futures.submit(
483                () -> {
484                    final Question question = new Question(dnsName, Record.TYPE.getType(type));
485                    if (!DNSSECLESS_TLDS.contains(dnsName.getLabels()[0].toString())) {
486                        for (int i = 0; i < 5; i++) {
487                            if (System.currentTimeMillis() - start > 5000) {
488                                Log.d(Config.LOGTAG, "DNS taking too long, abort DNSSEC retries after " + i + " for " + type.getSimpleName() + " " + dnsName);
489                                break;
490                            }
491                            Log.d(Config.LOGTAG, "DNSSEC try " + i + " for " + type.getSimpleName() + " " + dnsName);
492                            try {
493                                ResolverResult<D> result = DnssecResolverApi.INSTANCE.resolve(question);
494                                if (result.wasSuccessful() && !result.isAuthenticData()) {
495                                    Log.d(Config.LOGTAG, "DNSSEC validation failed for " + type.getSimpleName() + " : " + result.getUnverifiedReasons());
496                                }
497                                return result;
498                            } catch (DnssecValidationFailedException e) {
499                                Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + dnsName + " with DNSSEC. Try: " + i, e);
500                                // Try again, may be transient DNSSEC failure https://github.com/MiniDNS/minidns/issues/132
501                                if ("CNAME".equals(type.getSimpleName())) break; // CNAME failure on NXDOMAIN is common don't retry?
502                            } catch (Throwable throwable) {
503                                Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable);
504                                break;
505                            }
506                        }
507                    }
508                    return ResolverApi.INSTANCE.resolve(question);
509                },
510                DNS_QUERY_EXECUTOR);
511    }
512
513    public static class Result {
514        public static final String DOMAIN = "domain";
515        public static final String IP = "ip";
516        public static final String HOSTNAME = "hostname";
517        public static final String PORT = "port";
518        public static final String PRIORITY = "priority";
519        public static final String DIRECT_TLS = "directTls";
520        public static final String AUTHENTICATED = "authenticated";
521        private InetAddress ip;
522        private DnsName hostname;
523        private int port = XMPP_PORT_STARTTLS;
524        private boolean directTls = false;
525        private boolean authenticated = false;
526        private int priority;
527
528        static Result fromRecord(final SRV srv, final boolean directTls) {
529            final Result result = new Result();
530            result.port = srv.port;
531            result.hostname = srv.target;
532            result.directTls = directTls;
533            result.priority = srv.priority;
534            return result;
535        }
536
537        static List<Result> createWithDefaultPorts(final DnsName hostname, final InetAddress ip) {
538            return Lists.transform(
539                    Arrays.asList(XMPP_PORT_STARTTLS),
540                    p -> createDefault(hostname, ip, p, false));
541        }
542
543        static Result createDefault(final DnsName hostname, final InetAddress ip, final int port, final boolean authenticated) {
544            Result result = new Result();
545            result.port = port;
546            result.hostname = hostname;
547            result.ip = ip;
548            result.authenticated = authenticated;
549            return result;
550        }
551
552        static Result createDefault(final DnsName hostname, final InetAddress ip, final boolean authenticated) {
553            return createDefault(hostname, ip, XMPP_PORT_STARTTLS, authenticated);
554        }
555
556        static Result createDefault(final DnsName hostname) {
557            return createDefault(hostname, null, XMPP_PORT_STARTTLS, false);
558        }
559
560        static List<Result> createDefaults(
561                final DnsName hostname, final Collection<InetAddress> inetAddresses) {
562            final ImmutableList.Builder<Result> builder = new ImmutableList.Builder<>();
563            for (final InetAddress inetAddress : inetAddresses) {
564                builder.addAll(createWithDefaultPorts(hostname, inetAddress));
565            }
566            return builder.build();
567        }
568
569        static List<Result> createDefaults(final DnsName hostname) {
570            return createWithDefaultPorts(hostname, null);
571        }
572
573        public static Result fromCursor(final Cursor cursor) {
574            final Result result = new Result();
575            try {
576                result.ip =
577                        InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndexOrThrow(IP)));
578            } catch (final UnknownHostException e) {
579                result.ip = null;
580            }
581            final String hostname = cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME));
582            result.hostname = hostname == null ? null : DnsName.from(hostname);
583            result.port = cursor.getInt(cursor.getColumnIndexOrThrow(PORT));
584            result.priority = cursor.getInt(cursor.getColumnIndexOrThrow(PRIORITY));
585            result.authenticated = cursor.getInt(cursor.getColumnIndexOrThrow(AUTHENTICATED)) > 0;
586            result.directTls = cursor.getInt(cursor.getColumnIndexOrThrow(DIRECT_TLS)) > 0;
587            return result;
588        }
589
590        @Override
591        public boolean equals(Object o) {
592            if (this == o) return true;
593            if (o == null || getClass() != o.getClass()) return false;
594            Result result = (Result) o;
595            return port == result.port
596                    && directTls == result.directTls
597                    && authenticated == result.authenticated
598                    && priority == result.priority
599                    && Objects.equal(ip, result.ip)
600                    && Objects.equal(hostname, result.hostname);
601        }
602
603        @Override
604        public int hashCode() {
605            return Objects.hashCode(ip, hostname, port, directTls, authenticated, priority);
606        }
607
608        public InetAddress getIp() {
609            return ip;
610        }
611
612        public int getPort() {
613            return port;
614        }
615
616        public DnsName getHostname() {
617            return hostname;
618        }
619
620        public boolean isDirectTls() {
621            return directTls;
622        }
623
624        public boolean isAuthenticated() {
625            return authenticated;
626        }
627
628        @Override
629        @NonNull
630        public String toString() {
631            return MoreObjects.toStringHelper(this)
632                    .add("ip", ip)
633                    .add("hostname", hostname)
634                    .add("port", port)
635                    .add("directTls", directTls)
636                    .add("authenticated", authenticated)
637                    .add("priority", priority)
638                    .toString();
639        }
640
641        public String asDestination() {
642            return ip != null ? InetAddresses.toAddrString(ip) : hostname.toString();
643        }
644
645        public ContentValues toContentValues() {
646            final ContentValues contentValues = new ContentValues();
647            contentValues.put(IP, ip == null ? null : ip.getAddress());
648            contentValues.put(HOSTNAME, hostname == null ? null : hostname.toString());
649            contentValues.put(PORT, port);
650            contentValues.put(PRIORITY, priority);
651            contentValues.put(DIRECT_TLS, directTls ? 1 : 0);
652            contentValues.put(AUTHENTICATED, authenticated ? 1 : 0);
653            return contentValues;
654        }
655
656        public Result seeOtherHost(final String seeOtherHost) {
657            final String hostname = seeOtherHost.trim();
658            if (hostname.isEmpty()) {
659                return null;
660            }
661            final Result result = new Result();
662            result.directTls = this.directTls;
663            final int portSegmentStart = hostname.lastIndexOf(':');
664            if (hostname.charAt(hostname.length() - 1) != ']'
665                    && portSegmentStart >= 0
666                    && hostname.length() >= portSegmentStart + 1) {
667                final String hostPart = hostname.substring(0, portSegmentStart);
668                final String portPart = hostname.substring(portSegmentStart + 1);
669                final Integer port = Ints.tryParse(portPart);
670                if (port == null || Strings.isNullOrEmpty(hostPart)) {
671                    return null;
672                }
673                final String host = eu.siacs.conversations.utils.IP.unwrapIPv6(hostPart);
674                result.port = port;
675                if (InetAddresses.isInetAddress(host)) {
676                    final InetAddress inetAddress;
677                    try {
678                        inetAddress = InetAddresses.forString(host);
679                    } catch (final IllegalArgumentException e) {
680                        return null;
681                    }
682                    result.ip = inetAddress;
683                } else {
684                    if (hostPart.trim().isEmpty()) {
685                        return null;
686                    }
687                    try {
688                        result.hostname = DnsName.from(hostPart.trim());
689                    } catch (final Exception e) {
690                        return null;
691                    }
692                }
693            } else {
694                final String host = eu.siacs.conversations.utils.IP.unwrapIPv6(hostname);
695                if (InetAddresses.isInetAddress(host)) {
696                    final InetAddress inetAddress;
697                    try {
698                        inetAddress = InetAddresses.forString(host);
699                    } catch (final IllegalArgumentException e) {
700                        return null;
701                    }
702                    result.ip = inetAddress;
703                } else {
704                    try {
705                        result.hostname = DnsName.from(hostname);
706                    } catch (final Exception e) {
707                        return null;
708                    }
709                }
710                result.port = port;
711            }
712            return result;
713        }
714    }
715}