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}