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