1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.resolver.dns;
18
19 import io.netty.buffer.ByteBuf;
20 import io.netty.buffer.ByteBufHolder;
21 import io.netty.channel.AddressedEnvelope;
22 import io.netty.channel.Channel;
23 import io.netty.channel.ChannelFuture;
24 import io.netty.channel.EventLoop;
25 import io.netty.handler.codec.CorruptedFrameException;
26 import io.netty.handler.codec.dns.DefaultDnsQuestion;
27 import io.netty.handler.codec.dns.DefaultDnsRecordDecoder;
28 import io.netty.handler.codec.dns.DnsQuestion;
29 import io.netty.handler.codec.dns.DnsRawRecord;
30 import io.netty.handler.codec.dns.DnsRecord;
31 import io.netty.handler.codec.dns.DnsRecordType;
32 import io.netty.handler.codec.dns.DnsResponse;
33 import io.netty.handler.codec.dns.DnsResponseCode;
34 import io.netty.handler.codec.dns.DnsSection;
35 import io.netty.util.NetUtil;
36 import io.netty.util.ReferenceCountUtil;
37 import io.netty.util.concurrent.Future;
38 import io.netty.util.concurrent.FutureListener;
39 import io.netty.util.concurrent.Promise;
40 import io.netty.util.internal.ObjectUtil;
41 import io.netty.util.internal.PlatformDependent;
42 import io.netty.util.internal.StringUtil;
43 import io.netty.util.internal.SuppressJava6Requirement;
44 import io.netty.util.internal.SystemPropertyUtil;
45 import io.netty.util.internal.ThrowableUtil;
46 import io.netty.util.internal.logging.InternalLogger;
47 import io.netty.util.internal.logging.InternalLoggerFactory;
48
49 import java.net.InetAddress;
50 import java.net.InetSocketAddress;
51 import java.net.UnknownHostException;
52 import java.util.AbstractList;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.IdentityHashMap;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Locale;
61 import java.util.Map;
62 import java.util.NoSuchElementException;
63 import java.util.Set;
64
65 import static io.netty.handler.codec.dns.DnsResponseCode.NXDOMAIN;
66 import static io.netty.handler.codec.dns.DnsResponseCode.SERVFAIL;
67 import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
68 import static java.lang.Math.min;
69
70 abstract class DnsResolveContext<T> {
71 private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsResolveContext.class);
72 private static final String PROP_TRY_FINAL_CNAME_ON_ADDRESS_LOOKUPS =
73 "io.netty.resolver.dns.tryCnameOnAddressLookups";
74 static boolean TRY_FINAL_CNAME_ON_ADDRESS_LOOKUPS;
75
76 static {
77 TRY_FINAL_CNAME_ON_ADDRESS_LOOKUPS =
78 SystemPropertyUtil.getBoolean(PROP_TRY_FINAL_CNAME_ON_ADDRESS_LOOKUPS, false);
79
80 if (logger.isDebugEnabled()) {
81 logger.debug("-D{}: {}", PROP_TRY_FINAL_CNAME_ON_ADDRESS_LOOKUPS, TRY_FINAL_CNAME_ON_ADDRESS_LOOKUPS);
82 }
83 }
84
85 private static final RuntimeException NXDOMAIN_QUERY_FAILED_EXCEPTION =
86 DnsResolveContextException.newStatic("No answer found and NXDOMAIN response code returned",
87 DnsResolveContext.class, "onResponse(..)");
88 private static final RuntimeException CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION =
89 DnsResolveContextException.newStatic("No matching CNAME record found",
90 DnsResolveContext.class, "onResponseCNAME(..)");
91 private static final RuntimeException NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION =
92 DnsResolveContextException.newStatic("No matching record type found",
93 DnsResolveContext.class, "onResponseAorAAAA(..)");
94 private static final RuntimeException UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION =
95 DnsResolveContextException.newStatic("Response type was unrecognized",
96 DnsResolveContext.class, "onResponse(..)");
97 private static final RuntimeException NAME_SERVERS_EXHAUSTED_EXCEPTION =
98 DnsResolveContextException.newStatic("No name servers returned an answer",
99 DnsResolveContext.class, "tryToFinishResolve(..)");
100 private static final RuntimeException SERVFAIL_QUERY_FAILED_EXCEPTION =
101 DnsErrorCauseException.newStatic("Query failed with SERVFAIL", SERVFAIL,
102 DnsResolveContext.class, "onResponse(..)");
103 private static final RuntimeException NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION =
104 DnsErrorCauseException.newStatic("Query failed with NXDOMAIN", NXDOMAIN,
105 DnsResolveContext.class, "onResponse(..)");
106
107 final DnsNameResolver parent;
108 private final Channel channel;
109 private final Promise<?> originalPromise;
110 private final DnsServerAddressStream nameServerAddrs;
111 private final String hostname;
112 private final int dnsClass;
113 private final DnsRecordType[] expectedTypes;
114 final DnsRecord[] additionals;
115
116 private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
117 Collections.newSetFromMap(
118 new IdentityHashMap<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>, Boolean>());
119
120 private List<T> finalResult;
121 private int allowedQueries;
122 private boolean triedCNAME;
123 private boolean completeEarly;
124
125 DnsResolveContext(DnsNameResolver parent, Channel channel,
126 Promise<?> originalPromise, String hostname, int dnsClass, DnsRecordType[] expectedTypes,
127 DnsRecord[] additionals, DnsServerAddressStream nameServerAddrs, int allowedQueries) {
128 assert expectedTypes.length > 0;
129
130 this.parent = parent;
131 this.channel = channel;
132 this.originalPromise = originalPromise;
133 this.hostname = hostname;
134 this.dnsClass = dnsClass;
135 this.expectedTypes = expectedTypes;
136 this.additionals = additionals;
137
138 this.nameServerAddrs = ObjectUtil.checkNotNull(nameServerAddrs, "nameServerAddrs");
139 this.allowedQueries = allowedQueries;
140 }
141
142 static final class DnsResolveContextException extends RuntimeException {
143
144 private static final long serialVersionUID = 1209303419266433003L;
145
146 private DnsResolveContextException(String message) {
147 super(message);
148 }
149
150 @SuppressJava6Requirement(reason = "uses Java 7+ Exception.<init>(String, Throwable, boolean, boolean)" +
151 " but is guarded by version checks")
152 private DnsResolveContextException(String message, boolean shared) {
153 super(message, null, false, true);
154 assert shared;
155 }
156
157
158
159 @Override
160 public Throwable fillInStackTrace() {
161 return this;
162 }
163
164 static DnsResolveContextException newStatic(String message, Class<?> clazz, String method) {
165 final DnsResolveContextException exception;
166 if (PlatformDependent.javaVersion() >= 7) {
167 exception = new DnsResolveContextException(message, true);
168 } else {
169 exception = new DnsResolveContextException(message);
170 }
171 return ThrowableUtil.unknownStackTrace(exception, clazz, method);
172 }
173 }
174
175
176
177
178 Channel channel() {
179 return channel;
180 }
181
182
183
184
185 DnsCache resolveCache() {
186 return parent.resolveCache();
187 }
188
189
190
191
192 DnsCnameCache cnameCache() {
193 return parent.cnameCache();
194 }
195
196
197
198
199 AuthoritativeDnsServerCache authoritativeDnsServerCache() {
200 return parent.authoritativeDnsServerCache();
201 }
202
203
204
205
206 abstract DnsResolveContext<T> newResolverContext(DnsNameResolver parent, Channel channel,
207 Promise<?> originalPromise,
208 String hostname,
209 int dnsClass, DnsRecordType[] expectedTypes,
210 DnsRecord[] additionals,
211 DnsServerAddressStream nameServerAddrs, int allowedQueries);
212
213
214
215
216 abstract T convertRecord(DnsRecord record, String hostname, DnsRecord[] additionals, EventLoop eventLoop);
217
218
219
220
221
222 abstract List<T> filterResults(List<T> unfiltered);
223
224 abstract boolean isCompleteEarly(T resolved);
225
226
227
228
229
230 abstract boolean isDuplicateAllowed();
231
232
233
234
235 abstract void cache(String hostname, DnsRecord[] additionals,
236 DnsRecord result, T convertedResult);
237
238
239
240
241 abstract void cache(String hostname, DnsRecord[] additionals,
242 UnknownHostException cause);
243
244 void resolve(final Promise<List<T>> promise) {
245 final String[] searchDomains = parent.searchDomains();
246 if (searchDomains.length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) {
247 internalResolve(hostname, promise);
248 } else {
249 final boolean startWithoutSearchDomain = hasNDots();
250 final String initialHostname = startWithoutSearchDomain ? hostname : hostname + '.' + searchDomains[0];
251 final int initialSearchDomainIdx = startWithoutSearchDomain ? 0 : 1;
252
253 final Promise<List<T>> searchDomainPromise = parent.executor().newPromise();
254 searchDomainPromise.addListener(new FutureListener<List<T>>() {
255 private int searchDomainIdx = initialSearchDomainIdx;
256 @Override
257 public void operationComplete(Future<List<T>> future) {
258 Throwable cause = future.cause();
259 if (cause == null) {
260 final List<T> result = future.getNow();
261 if (!promise.trySuccess(result)) {
262 for (T item : result) {
263 ReferenceCountUtil.safeRelease(item);
264 }
265 }
266 } else {
267 if (DnsNameResolver.isTransportOrTimeoutError(cause)) {
268 promise.tryFailure(new SearchDomainUnknownHostException(cause, hostname, expectedTypes,
269 searchDomains));
270 } else if (searchDomainIdx < searchDomains.length) {
271 Promise<List<T>> newPromise = parent.executor().newPromise();
272 newPromise.addListener(this);
273 doSearchDomainQuery(hostname + '.' + searchDomains[searchDomainIdx++], newPromise);
274 } else if (!startWithoutSearchDomain) {
275 internalResolve(hostname, promise);
276 } else {
277 promise.tryFailure(new SearchDomainUnknownHostException(cause, hostname, expectedTypes,
278 searchDomains));
279 }
280 }
281 }
282 });
283 doSearchDomainQuery(initialHostname, searchDomainPromise);
284 }
285 }
286
287 private boolean hasNDots() {
288 for (int idx = hostname.length() - 1, dots = 0; idx >= 0; idx--) {
289 if (hostname.charAt(idx) == '.' && ++dots >= parent.ndots()) {
290 return true;
291 }
292 }
293 return false;
294 }
295
296 private static final class SearchDomainUnknownHostException extends UnknownHostException {
297 private static final long serialVersionUID = -8573510133644997085L;
298
299 SearchDomainUnknownHostException(Throwable cause, String originalHostname,
300 DnsRecordType[] queryTypes, String[] searchDomains) {
301 super("Failed to resolve '" + originalHostname + "' " + Arrays.toString(queryTypes) +
302 " and search domain query for configured domains failed as well: " +
303 Arrays.toString(searchDomains));
304 setStackTrace(cause.getStackTrace());
305
306 initCause(cause.getCause());
307 }
308
309
310 @Override
311 public Throwable fillInStackTrace() {
312 return this;
313 }
314 }
315
316 void doSearchDomainQuery(String hostname, Promise<List<T>> nextPromise) {
317 DnsResolveContext<T> nextContext = newResolverContext(parent, channel,
318 originalPromise, hostname, dnsClass,
319 expectedTypes, additionals, nameServerAddrs,
320 parent.maxQueriesPerResolve());
321 nextContext.internalResolve(hostname, nextPromise);
322 }
323
324 private static String hostnameWithDot(String name) {
325 if (StringUtil.endsWith(name, '.')) {
326 return name;
327 }
328 return name + '.';
329 }
330
331
332
333
334
335 static String cnameResolveFromCache(DnsCnameCache cnameCache, String name) throws UnknownHostException {
336 String first = cnameCache.get(hostnameWithDot(name));
337 if (first == null) {
338
339 return name;
340 }
341
342 String second = cnameCache.get(hostnameWithDot(first));
343 if (second == null) {
344
345 return first;
346 }
347
348 checkCnameLoop(name, first, second);
349 return cnameResolveFromCacheLoop(cnameCache, name, first, second);
350 }
351
352 private static String cnameResolveFromCacheLoop(
353 DnsCnameCache cnameCache, String hostname, String first, String mapping) throws UnknownHostException {
354
355
356 boolean advance = false;
357
358 String name = mapping;
359
360 while ((mapping = cnameCache.get(hostnameWithDot(name))) != null) {
361 checkCnameLoop(hostname, first, mapping);
362 name = mapping;
363 if (advance) {
364 first = cnameCache.get(first);
365 }
366 advance = !advance;
367 }
368 return name;
369 }
370
371 private static void checkCnameLoop(String hostname, String first, String second) throws UnknownHostException {
372 if (first.equals(second)) {
373
374 throw new UnknownHostException("CNAME loop detected for '" + hostname + '\'');
375 }
376 }
377 private void internalResolve(String name, Promise<List<T>> promise) {
378 try {
379
380 name = cnameResolveFromCache(cnameCache(), name);
381 } catch (Throwable cause) {
382 promise.tryFailure(cause);
383 return;
384 }
385
386 try {
387 DnsServerAddressStream nameServerAddressStream = getNameServers(name);
388
389 final int end = expectedTypes.length - 1;
390 for (int i = 0; i < end; ++i) {
391 if (!query(name, expectedTypes[i], nameServerAddressStream.duplicate(), false, promise)) {
392 return;
393 }
394 }
395 query(name, expectedTypes[end], nameServerAddressStream, false, promise);
396 } finally {
397
398 channel.flush();
399 }
400 }
401
402
403
404
405
406 private DnsServerAddressStream getNameServersFromCache(String hostname) {
407 int len = hostname.length();
408
409 if (len == 0) {
410
411 return null;
412 }
413
414
415 if (hostname.charAt(len - 1) != '.') {
416 hostname += ".";
417 }
418
419 int idx = hostname.indexOf('.');
420 if (idx == hostname.length() - 1) {
421
422 return null;
423 }
424
425
426 for (;;) {
427
428 hostname = hostname.substring(idx + 1);
429
430 int idx2 = hostname.indexOf('.');
431 if (idx2 <= 0 || idx2 == hostname.length() - 1) {
432
433 return null;
434 }
435 idx = idx2;
436
437 DnsServerAddressStream entries = authoritativeDnsServerCache().get(hostname);
438 if (entries != null) {
439
440
441 return entries;
442 }
443 }
444 }
445
446 private void query(final DnsServerAddressStream nameServerAddrStream,
447 final int nameServerAddrStreamIndex,
448 final DnsQuestion question,
449 final DnsQueryLifecycleObserver queryLifecycleObserver,
450 final boolean flush,
451 final Promise<List<T>> promise,
452 final Throwable cause) {
453 if (completeEarly || nameServerAddrStreamIndex >= nameServerAddrStream.size() ||
454 allowedQueries == 0 || originalPromise.isCancelled() || promise.isCancelled()) {
455 tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question, queryLifecycleObserver,
456 promise, cause);
457 return;
458 }
459
460 --allowedQueries;
461
462 final InetSocketAddress nameServerAddr = nameServerAddrStream.next();
463 if (nameServerAddr.isUnresolved()) {
464 queryUnresolvedNameServer(nameServerAddr, nameServerAddrStream, nameServerAddrStreamIndex, question,
465 queryLifecycleObserver, promise, cause);
466 return;
467 }
468 final Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> queryPromise =
469 channel.eventLoop().newPromise();
470
471 final long queryStartTimeNanos;
472 final boolean isFeedbackAddressStream;
473 if (nameServerAddrStream instanceof DnsServerResponseFeedbackAddressStream) {
474 queryStartTimeNanos = System.nanoTime();
475 isFeedbackAddressStream = true;
476 } else {
477 queryStartTimeNanos = -1;
478 isFeedbackAddressStream = false;
479 }
480
481 final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f =
482 parent.doQuery(channel, nameServerAddr, question,
483 queryLifecycleObserver, additionals, flush, queryPromise);
484
485 queriesInProgress.add(f);
486
487 f.addListener(new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
488 @Override
489 public void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
490 queriesInProgress.remove(future);
491
492 if (promise.isDone() || future.isCancelled()) {
493 queryLifecycleObserver.queryCancelled(allowedQueries);
494
495
496
497 AddressedEnvelope<DnsResponse, InetSocketAddress> result = future.getNow();
498 if (result != null) {
499 result.release();
500 }
501 return;
502 }
503
504 final Throwable queryCause = future.cause();
505 try {
506 if (queryCause == null) {
507 if (isFeedbackAddressStream) {
508 final DnsServerResponseFeedbackAddressStream feedbackNameServerAddrStream =
509 (DnsServerResponseFeedbackAddressStream) nameServerAddrStream;
510 feedbackNameServerAddrStream.feedbackSuccess(nameServerAddr,
511 System.nanoTime() - queryStartTimeNanos);
512 }
513 onResponse(nameServerAddrStream, nameServerAddrStreamIndex, question, future.getNow(),
514 queryLifecycleObserver, promise);
515 } else {
516
517 if (isFeedbackAddressStream) {
518 final DnsServerResponseFeedbackAddressStream feedbackNameServerAddrStream =
519 (DnsServerResponseFeedbackAddressStream) nameServerAddrStream;
520 feedbackNameServerAddrStream.feedbackFailure(nameServerAddr, queryCause,
521 System.nanoTime() - queryStartTimeNanos);
522 }
523 queryLifecycleObserver.queryFailed(queryCause);
524 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
525 newDnsQueryLifecycleObserver(question), true, promise, queryCause);
526 }
527 } finally {
528 tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question,
529
530
531 NoopDnsQueryLifecycleObserver.INSTANCE,
532 promise, queryCause);
533 }
534 }
535 });
536 }
537
538 private void queryUnresolvedNameServer(final InetSocketAddress nameServerAddr,
539 final DnsServerAddressStream nameServerAddrStream,
540 final int nameServerAddrStreamIndex,
541 final DnsQuestion question,
542 final DnsQueryLifecycleObserver queryLifecycleObserver,
543 final Promise<List<T>> promise,
544 final Throwable cause) {
545 final String nameServerName = PlatformDependent.javaVersion() >= 7 ?
546 nameServerAddr.getHostString() : nameServerAddr.getHostName();
547 assert nameServerName != null;
548
549
550 final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> resolveFuture = parent.executor()
551 .newSucceededFuture(null);
552 queriesInProgress.add(resolveFuture);
553
554 Promise<List<InetAddress>> resolverPromise = parent.executor().newPromise();
555 resolverPromise.addListener(new FutureListener<List<InetAddress>>() {
556 @Override
557 public void operationComplete(final Future<List<InetAddress>> future) {
558
559 queriesInProgress.remove(resolveFuture);
560
561 if (future.isSuccess()) {
562 List<InetAddress> resolvedAddresses = future.getNow();
563 DnsServerAddressStream addressStream = new CombinedDnsServerAddressStream(
564 nameServerAddr, resolvedAddresses, nameServerAddrStream);
565 query(addressStream, nameServerAddrStreamIndex, question,
566 queryLifecycleObserver, true, promise, cause);
567 } else {
568
569 query(nameServerAddrStream, nameServerAddrStreamIndex + 1,
570 question, queryLifecycleObserver, true, promise, cause);
571 }
572 }
573 });
574 DnsCache resolveCache = resolveCache();
575 if (!DnsNameResolver.doResolveAllCached(nameServerName, additionals, resolverPromise, resolveCache,
576 parent.searchDomains(), parent.ndots(), parent.resolvedInternetProtocolFamiliesUnsafe())) {
577
578 new DnsAddressResolveContext(parent, channel,
579 originalPromise, nameServerName, additionals, parent.newNameServerAddressStream(nameServerName),
580
581
582 allowedQueries,
583 resolveCache,
584 redirectAuthoritativeDnsServerCache(authoritativeDnsServerCache()), false)
585 .resolve(resolverPromise);
586 }
587 }
588
589 private static AuthoritativeDnsServerCache redirectAuthoritativeDnsServerCache(
590 AuthoritativeDnsServerCache authoritativeDnsServerCache) {
591
592
593 if (authoritativeDnsServerCache instanceof RedirectAuthoritativeDnsServerCache) {
594 return authoritativeDnsServerCache;
595 }
596 return new RedirectAuthoritativeDnsServerCache(authoritativeDnsServerCache);
597 }
598
599 private static final class RedirectAuthoritativeDnsServerCache implements AuthoritativeDnsServerCache {
600 private final AuthoritativeDnsServerCache wrapped;
601
602 RedirectAuthoritativeDnsServerCache(AuthoritativeDnsServerCache authoritativeDnsServerCache) {
603 this.wrapped = authoritativeDnsServerCache;
604 }
605
606 @Override
607 public DnsServerAddressStream get(String hostname) {
608
609
610 return null;
611 }
612
613 @Override
614 public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
615 wrapped.cache(hostname, address, originalTtl, loop);
616 }
617
618 @Override
619 public void clear() {
620 wrapped.clear();
621 }
622
623 @Override
624 public boolean clear(String hostname) {
625 return wrapped.clear(hostname);
626 }
627 }
628
629 private void onResponse(final DnsServerAddressStream nameServerAddrStream, final int nameServerAddrStreamIndex,
630 final DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
631 final DnsQueryLifecycleObserver queryLifecycleObserver,
632 Promise<List<T>> promise) {
633 try {
634 final DnsResponse res = envelope.content();
635 final DnsResponseCode code = res.code();
636 if (code == DnsResponseCode.NOERROR) {
637 if (handleRedirect(question, envelope, queryLifecycleObserver, promise)) {
638
639 return;
640 }
641 final DnsRecordType type = question.type();
642
643 if (type == DnsRecordType.CNAME) {
644 onResponseCNAME(question, buildAliasMap(envelope.content(), cnameCache(), parent.executor()),
645 queryLifecycleObserver, promise);
646 return;
647 }
648
649 for (DnsRecordType expectedType : expectedTypes) {
650 if (type == expectedType) {
651 onExpectedResponse(question, envelope, queryLifecycleObserver, promise);
652 return;
653 }
654 }
655
656 queryLifecycleObserver.queryFailed(UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION);
657 return;
658 }
659
660
661 if (code != DnsResponseCode.NXDOMAIN) {
662 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
663 queryLifecycleObserver.queryNoAnswer(code), true, promise, cause(code));
664 } else {
665 queryLifecycleObserver.queryFailed(NXDOMAIN_QUERY_FAILED_EXCEPTION);
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685 if (!res.isAuthoritativeAnswer()) {
686 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
687 newDnsQueryLifecycleObserver(question), true, promise, cause(code));
688 } else {
689
690 tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question,
691 queryLifecycleObserver, promise, NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION);
692 }
693 }
694 } finally {
695 ReferenceCountUtil.safeRelease(envelope);
696 }
697 }
698
699
700
701
702 private boolean handleRedirect(
703 DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
704 final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
705 final DnsResponse res = envelope.content();
706
707
708 if (res.count(DnsSection.ANSWER) == 0) {
709 AuthoritativeNameServerList serverNames = extractAuthoritativeNameServers(question.name(), res);
710 if (serverNames != null) {
711 int additionalCount = res.count(DnsSection.ADDITIONAL);
712
713 AuthoritativeDnsServerCache authoritativeDnsServerCache = authoritativeDnsServerCache();
714 for (int i = 0; i < additionalCount; i++) {
715 final DnsRecord r = res.recordAt(DnsSection.ADDITIONAL, i);
716
717 if (r.type() == DnsRecordType.A && !parent.supportsARecords() ||
718 r.type() == DnsRecordType.AAAA && !parent.supportsAAAARecords()) {
719 continue;
720 }
721
722
723
724 serverNames.handleWithAdditional(parent, r, authoritativeDnsServerCache);
725 }
726
727
728 serverNames.handleWithoutAdditionals(parent, resolveCache(), authoritativeDnsServerCache);
729
730 List<InetSocketAddress> addresses = serverNames.addressList();
731
732
733 DnsServerAddressStream serverStream = parent.newRedirectDnsServerStream(
734 question.name(), addresses);
735
736 if (serverStream != null) {
737 query(serverStream, 0, question,
738 queryLifecycleObserver.queryRedirected(new DnsAddressStreamList(serverStream)),
739 true, promise, null);
740 return true;
741 }
742 }
743 }
744 return false;
745 }
746
747 private static Throwable cause(final DnsResponseCode code) {
748 assert code != null;
749 if (SERVFAIL.intValue() == code.intValue()) {
750 return SERVFAIL_QUERY_FAILED_EXCEPTION;
751 } else if (NXDOMAIN.intValue() == code.intValue()) {
752 return NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION;
753 }
754
755 return null;
756 }
757
758 private static final class DnsAddressStreamList extends AbstractList<InetSocketAddress> {
759
760 private final DnsServerAddressStream duplicate;
761 private List<InetSocketAddress> addresses;
762
763 DnsAddressStreamList(DnsServerAddressStream stream) {
764 duplicate = stream.duplicate();
765 }
766
767 @Override
768 public InetSocketAddress get(int index) {
769 if (addresses == null) {
770 DnsServerAddressStream stream = duplicate.duplicate();
771 addresses = new ArrayList<InetSocketAddress>(size());
772 for (int i = 0; i < stream.size(); i++) {
773 addresses.add(stream.next());
774 }
775 }
776 return addresses.get(index);
777 }
778
779 @Override
780 public int size() {
781 return duplicate.size();
782 }
783
784 @Override
785 public Iterator<InetSocketAddress> iterator() {
786 return new Iterator<InetSocketAddress>() {
787 private final DnsServerAddressStream stream = duplicate.duplicate();
788 private int i;
789
790 @Override
791 public boolean hasNext() {
792 return i < stream.size();
793 }
794
795 @Override
796 public InetSocketAddress next() {
797 if (!hasNext()) {
798 throw new NoSuchElementException();
799 }
800 i++;
801 return stream.next();
802 }
803
804 @Override
805 public void remove() {
806 throw new UnsupportedOperationException();
807 }
808 };
809 }
810 }
811
812
813
814
815
816 private static AuthoritativeNameServerList extractAuthoritativeNameServers(String questionName, DnsResponse res) {
817 int authorityCount = res.count(DnsSection.AUTHORITY);
818 if (authorityCount == 0) {
819 return null;
820 }
821
822 AuthoritativeNameServerList serverNames = new AuthoritativeNameServerList(questionName);
823 for (int i = 0; i < authorityCount; i++) {
824 serverNames.add(res.recordAt(DnsSection.AUTHORITY, i));
825 }
826 return serverNames.isEmpty() ? null : serverNames;
827 }
828
829 private void onExpectedResponse(
830 DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
831 final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
832
833
834 final DnsResponse response = envelope.content();
835 final Map<String, String> cnames = buildAliasMap(response, cnameCache(), parent.executor());
836 final int answerCount = response.count(DnsSection.ANSWER);
837
838 boolean found = false;
839 boolean completeEarly = this.completeEarly;
840 boolean cnameNeedsFollow = !cnames.isEmpty();
841 for (int i = 0; i < answerCount; i ++) {
842 final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
843 final DnsRecordType type = r.type();
844 boolean matches = false;
845 for (DnsRecordType expectedType : expectedTypes) {
846 if (type == expectedType) {
847 matches = true;
848 break;
849 }
850 }
851
852 if (!matches) {
853 continue;
854 }
855
856 final String questionName = question.name().toLowerCase(Locale.US);
857 final String recordName = r.name().toLowerCase(Locale.US);
858
859
860 if (!recordName.equals(questionName)) {
861 Map<String, String> cnamesCopy = new HashMap<String, String>(cnames);
862
863 String resolved = questionName;
864 do {
865 resolved = cnamesCopy.remove(resolved);
866 if (recordName.equals(resolved)) {
867
868 cnameNeedsFollow = false;
869 break;
870 }
871 } while (resolved != null);
872
873 if (resolved == null) {
874 assert questionName.isEmpty() || questionName.charAt(questionName.length() - 1) == '.';
875
876 for (String searchDomain : parent.searchDomains()) {
877 if (searchDomain.isEmpty()) {
878 continue;
879 }
880
881 final String fqdn;
882 if (searchDomain.charAt(searchDomain.length() - 1) == '.') {
883 fqdn = questionName + searchDomain;
884 } else {
885 fqdn = questionName + searchDomain + '.';
886 }
887 if (recordName.equals(fqdn)) {
888 resolved = recordName;
889 break;
890 }
891 }
892 if (resolved == null) {
893 if (logger.isDebugEnabled()) {
894 logger.debug("{} Ignoring record {} for [{}: {}] as it contains a different name than " +
895 "the question name [{}]. Cnames: {}, Search domains: {}",
896 channel, r.toString(), response.id(), envelope.sender(),
897 questionName, cnames, parent.searchDomains());
898 }
899 continue;
900 }
901 }
902 }
903
904 final T converted = convertRecord(r, hostname, additionals, parent.executor());
905 if (converted == null) {
906 if (logger.isDebugEnabled()) {
907 logger.debug("{} Ignoring record {} for [{}: {}] as the converted record is null. "
908 + "Hostname [{}], Additionals: {}",
909 channel, r.toString(), response.id(),
910 envelope.sender(), hostname, additionals);
911 }
912 continue;
913 }
914
915 boolean shouldRelease = false;
916
917
918 if (!completeEarly) {
919 completeEarly = isCompleteEarly(converted);
920 }
921
922
923
924 if (!promise.isDone()) {
925
926
927
928
929
930 if (finalResult == null) {
931 finalResult = new ArrayList<T>(8);
932 finalResult.add(converted);
933 } else if (isDuplicateAllowed() || !finalResult.contains(converted)) {
934 finalResult.add(converted);
935 } else {
936 shouldRelease = true;
937 }
938 } else {
939 shouldRelease = true;
940 }
941
942 cache(hostname, additionals, r, converted);
943 found = true;
944
945 if (shouldRelease) {
946 ReferenceCountUtil.release(converted);
947 }
948
949 }
950
951 if (found && !cnameNeedsFollow) {
952
953
954 if (completeEarly) {
955 this.completeEarly = true;
956 }
957 queryLifecycleObserver.querySucceed();
958 } else if (cnames.isEmpty()) {
959 queryLifecycleObserver.queryFailed(NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION);
960 } else {
961 queryLifecycleObserver.querySucceed();
962
963 onResponseCNAME(question, cnames, newDnsQueryLifecycleObserver(question), promise);
964 }
965 }
966
967 private void onResponseCNAME(
968 DnsQuestion question, Map<String, String> cnames,
969 final DnsQueryLifecycleObserver queryLifecycleObserver,
970 Promise<List<T>> promise) {
971
972
973 String resolved = question.name().toLowerCase(Locale.US);
974 boolean found = false;
975 while (!cnames.isEmpty()) {
976
977
978 final String next = cnames.remove(resolved);
979 if (next != null) {
980 found = true;
981 resolved = next;
982 } else {
983 break;
984 }
985 }
986
987 if (found) {
988 followCname(question, resolved, queryLifecycleObserver, promise);
989 } else {
990 queryLifecycleObserver.queryFailed(CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION);
991 }
992 }
993
994 private static Map<String, String> buildAliasMap(DnsResponse response, DnsCnameCache cache, EventLoop loop) {
995 final int answerCount = response.count(DnsSection.ANSWER);
996 Map<String, String> cnames = null;
997 for (int i = 0; i < answerCount; i ++) {
998 final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
999 final DnsRecordType type = r.type();
1000 if (type != DnsRecordType.CNAME) {
1001 continue;
1002 }
1003
1004 if (!(r instanceof DnsRawRecord)) {
1005 continue;
1006 }
1007
1008 final ByteBuf recordContent = ((ByteBufHolder) r).content();
1009 final String domainName = decodeDomainName(recordContent);
1010 if (domainName == null) {
1011 continue;
1012 }
1013
1014 if (cnames == null) {
1015 cnames = new HashMap<String, String>(min(8, answerCount));
1016 }
1017
1018 String name = r.name().toLowerCase(Locale.US);
1019 String mapping = domainName.toLowerCase(Locale.US);
1020
1021
1022 String nameWithDot = hostnameWithDot(name);
1023 String mappingWithDot = hostnameWithDot(mapping);
1024 if (!nameWithDot.equalsIgnoreCase(mappingWithDot)) {
1025 cache.cache(nameWithDot, mappingWithDot, r.timeToLive(), loop);
1026 cnames.put(name, mapping);
1027 }
1028 }
1029
1030 return cnames != null? cnames : Collections.<String, String>emptyMap();
1031 }
1032
1033 private void tryToFinishResolve(final DnsServerAddressStream nameServerAddrStream,
1034 final int nameServerAddrStreamIndex,
1035 final DnsQuestion question,
1036 final DnsQueryLifecycleObserver queryLifecycleObserver,
1037 final Promise<List<T>> promise,
1038 final Throwable cause) {
1039
1040
1041 if (!completeEarly && !queriesInProgress.isEmpty()) {
1042 queryLifecycleObserver.queryCancelled(allowedQueries);
1043
1044
1045
1046 return;
1047 }
1048
1049
1050 if (finalResult == null) {
1051 if (nameServerAddrStreamIndex < nameServerAddrStream.size()) {
1052 if (queryLifecycleObserver == NoopDnsQueryLifecycleObserver.INSTANCE) {
1053
1054
1055 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
1056 newDnsQueryLifecycleObserver(question), true, promise, cause);
1057 } else {
1058 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, queryLifecycleObserver,
1059 true, promise, cause);
1060 }
1061 return;
1062 }
1063
1064 queryLifecycleObserver.queryFailed(NAME_SERVERS_EXHAUSTED_EXCEPTION);
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077 if (TRY_FINAL_CNAME_ON_ADDRESS_LOOKUPS) {
1078
1079
1080
1081 final boolean isValidResponse =
1082 cause == NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION || cause == SERVFAIL_QUERY_FAILED_EXCEPTION;
1083 if ((cause == null || isValidResponse) && !triedCNAME &&
1084 (question.type() == DnsRecordType.A || question.type() == DnsRecordType.AAAA)) {
1085
1086 triedCNAME = true;
1087
1088 query(hostname, DnsRecordType.CNAME, getNameServers(hostname), true, promise);
1089 return;
1090 }
1091 }
1092 } else {
1093 queryLifecycleObserver.queryCancelled(allowedQueries);
1094 }
1095
1096
1097 finishResolve(promise, cause);
1098 }
1099
1100 private void finishResolve(Promise<List<T>> promise, Throwable cause) {
1101
1102
1103 if (!completeEarly && !queriesInProgress.isEmpty()) {
1104
1105 for (Iterator<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> i = queriesInProgress.iterator();
1106 i.hasNext();) {
1107 Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = i.next();
1108 i.remove();
1109
1110 f.cancel(false);
1111 }
1112 }
1113
1114 if (finalResult != null) {
1115 if (!promise.isDone()) {
1116
1117 final List<T> result = filterResults(finalResult);
1118
1119 finalResult = Collections.emptyList();
1120 if (!DnsNameResolver.trySuccess(promise, result)) {
1121 for (T item : result) {
1122 ReferenceCountUtil.safeRelease(item);
1123 }
1124 }
1125 } else {
1126
1127
1128 assert finalResult.isEmpty();
1129 }
1130 return;
1131 }
1132
1133
1134 final int maxAllowedQueries = parent.maxQueriesPerResolve();
1135 final int tries = maxAllowedQueries - allowedQueries;
1136 final StringBuilder buf = new StringBuilder(64);
1137
1138 buf.append("Failed to resolve '").append(hostname).append("' ").append(Arrays.toString(expectedTypes));
1139 if (tries > 1) {
1140 if (tries < maxAllowedQueries) {
1141 buf.append(" after ")
1142 .append(tries)
1143 .append(" queries ");
1144 } else {
1145 buf.append(". Exceeded max queries per resolve ")
1146 .append(maxAllowedQueries)
1147 .append(' ');
1148 }
1149 }
1150 final UnknownHostException unknownHostException = new UnknownHostException(buf.toString());
1151 if (cause == null) {
1152
1153
1154 cache(hostname, additionals, unknownHostException);
1155 } else {
1156 unknownHostException.initCause(cause);
1157 }
1158 promise.tryFailure(unknownHostException);
1159 }
1160
1161 static String decodeDomainName(ByteBuf in) {
1162 in.markReaderIndex();
1163 try {
1164 return DefaultDnsRecordDecoder.decodeName(in);
1165 } catch (CorruptedFrameException e) {
1166
1167 return null;
1168 } finally {
1169 in.resetReaderIndex();
1170 }
1171 }
1172
1173 private DnsServerAddressStream getNameServers(String name) {
1174 DnsServerAddressStream stream = getNameServersFromCache(name);
1175 if (stream == null) {
1176
1177
1178
1179 if (name.equals(hostname)) {
1180 return nameServerAddrs.duplicate();
1181 }
1182 return parent.newNameServerAddressStream(name);
1183 }
1184 return stream;
1185 }
1186
1187 private void followCname(DnsQuestion question, String cname, DnsQueryLifecycleObserver queryLifecycleObserver,
1188 Promise<List<T>> promise) {
1189 final DnsQuestion cnameQuestion;
1190 final DnsServerAddressStream stream;
1191 try {
1192 cname = cnameResolveFromCache(cnameCache(), cname);
1193 stream = getNameServers(cname);
1194 cnameQuestion = new DefaultDnsQuestion(cname, question.type(), dnsClass);
1195 } catch (Throwable cause) {
1196 queryLifecycleObserver.queryFailed(cause);
1197 PlatformDependent.throwException(cause);
1198 return;
1199 }
1200 query(stream, 0, cnameQuestion, queryLifecycleObserver.queryCNAMEd(cnameQuestion),
1201 true, promise, null);
1202 }
1203
1204 private boolean query(String hostname, DnsRecordType type, DnsServerAddressStream dnsServerAddressStream,
1205 boolean flush, Promise<List<T>> promise) {
1206 final DnsQuestion question;
1207 try {
1208 question = new DefaultDnsQuestion(hostname, type, dnsClass);
1209 } catch (Throwable cause) {
1210
1211
1212 promise.tryFailure(new IllegalArgumentException("Unable to create DNS Question for: [" + hostname + ", " +
1213 type + ']', cause));
1214 return false;
1215 }
1216 query(dnsServerAddressStream, 0, question, newDnsQueryLifecycleObserver(question), flush, promise, null);
1217 return true;
1218 }
1219
1220 private DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsQuestion question) {
1221 return parent.dnsQueryLifecycleObserverFactory().newDnsQueryLifecycleObserver(question);
1222 }
1223
1224 private final class CombinedDnsServerAddressStream implements DnsServerAddressStream {
1225 private final InetSocketAddress replaced;
1226 private final DnsServerAddressStream originalStream;
1227 private final List<InetAddress> resolvedAddresses;
1228 private Iterator<InetAddress> resolved;
1229
1230 CombinedDnsServerAddressStream(InetSocketAddress replaced, List<InetAddress> resolvedAddresses,
1231 DnsServerAddressStream originalStream) {
1232 this.replaced = replaced;
1233 this.resolvedAddresses = resolvedAddresses;
1234 this.originalStream = originalStream;
1235 resolved = resolvedAddresses.iterator();
1236 }
1237
1238 @Override
1239 public InetSocketAddress next() {
1240 if (resolved.hasNext()) {
1241 return nextResolved0();
1242 }
1243 InetSocketAddress address = originalStream.next();
1244 if (address.equals(replaced)) {
1245 resolved = resolvedAddresses.iterator();
1246 return nextResolved0();
1247 }
1248 return address;
1249 }
1250
1251 private InetSocketAddress nextResolved0() {
1252 return parent.newRedirectServerAddress(resolved.next());
1253 }
1254
1255 @Override
1256 public int size() {
1257 return originalStream.size() + resolvedAddresses.size() - 1;
1258 }
1259
1260 @Override
1261 public DnsServerAddressStream duplicate() {
1262 return new CombinedDnsServerAddressStream(replaced, resolvedAddresses, originalStream.duplicate());
1263 }
1264 }
1265
1266
1267
1268
1269 private static final class AuthoritativeNameServerList {
1270
1271 private final String questionName;
1272
1273
1274 private AuthoritativeNameServer head;
1275
1276 private int nameServerCount;
1277
1278 AuthoritativeNameServerList(String questionName) {
1279 this.questionName = questionName.toLowerCase(Locale.US);
1280 }
1281
1282 void add(DnsRecord r) {
1283 if (r.type() != DnsRecordType.NS || !(r instanceof DnsRawRecord)) {
1284 return;
1285 }
1286
1287
1288 if (questionName.length() < r.name().length()) {
1289 return;
1290 }
1291
1292 String recordName = r.name().toLowerCase(Locale.US);
1293
1294 int dots = 0;
1295 for (int a = recordName.length() - 1, b = questionName.length() - 1; a >= 0; a--, b--) {
1296 char c = recordName.charAt(a);
1297 if (questionName.charAt(b) != c) {
1298 return;
1299 }
1300 if (c == '.') {
1301 dots++;
1302 }
1303 }
1304
1305 if (head != null && head.dots > dots) {
1306
1307 return;
1308 }
1309
1310 final ByteBuf recordContent = ((ByteBufHolder) r).content();
1311 final String domainName = decodeDomainName(recordContent);
1312 if (domainName == null) {
1313
1314 return;
1315 }
1316
1317
1318
1319 if (head == null || head.dots < dots) {
1320 nameServerCount = 1;
1321 head = new AuthoritativeNameServer(dots, r.timeToLive(), recordName, domainName);
1322 } else if (head.dots == dots) {
1323 AuthoritativeNameServer serverName = head;
1324 while (serverName.next != null) {
1325 serverName = serverName.next;
1326 }
1327 serverName.next = new AuthoritativeNameServer(dots, r.timeToLive(), recordName, domainName);
1328 nameServerCount++;
1329 }
1330 }
1331
1332 void handleWithAdditional(
1333 DnsNameResolver parent, DnsRecord r, AuthoritativeDnsServerCache authoritativeCache) {
1334
1335 AuthoritativeNameServer serverName = head;
1336
1337 String nsName = r.name();
1338 InetAddress resolved = decodeAddress(r, nsName, parent.isDecodeIdn());
1339 if (resolved == null) {
1340
1341 return;
1342 }
1343
1344 while (serverName != null) {
1345 if (serverName.nsName.equalsIgnoreCase(nsName)) {
1346 if (serverName.address != null) {
1347
1348
1349 while (serverName.next != null && serverName.next.isCopy) {
1350 serverName = serverName.next;
1351 }
1352 AuthoritativeNameServer server = new AuthoritativeNameServer(serverName);
1353 server.next = serverName.next;
1354 serverName.next = server;
1355 serverName = server;
1356
1357 nameServerCount++;
1358 }
1359
1360
1361 serverName.update(parent.newRedirectServerAddress(resolved), r.timeToLive());
1362
1363
1364 cache(serverName, authoritativeCache, parent.executor());
1365 return;
1366 }
1367 serverName = serverName.next;
1368 }
1369 }
1370
1371
1372 void handleWithoutAdditionals(
1373 DnsNameResolver parent, DnsCache cache, AuthoritativeDnsServerCache authoritativeCache) {
1374 AuthoritativeNameServer serverName = head;
1375
1376 while (serverName != null) {
1377 if (serverName.address == null) {
1378
1379 cacheUnresolved(serverName, authoritativeCache, parent.executor());
1380
1381
1382
1383 List<? extends DnsCacheEntry> entries = cache.get(serverName.nsName, null);
1384 if (entries != null && !entries.isEmpty()) {
1385 InetAddress address = entries.get(0).address();
1386
1387
1388 if (address != null) {
1389 serverName.update(parent.newRedirectServerAddress(address));
1390
1391 for (int i = 1; i < entries.size(); i++) {
1392 address = entries.get(i).address();
1393
1394 assert address != null :
1395 "Cache returned a cached failure, should never return anything else";
1396
1397 AuthoritativeNameServer server = new AuthoritativeNameServer(serverName);
1398 server.next = serverName.next;
1399 serverName.next = server;
1400 serverName = server;
1401 serverName.update(parent.newRedirectServerAddress(address));
1402
1403 nameServerCount++;
1404 }
1405 }
1406 }
1407 }
1408 serverName = serverName.next;
1409 }
1410 }
1411
1412 private static void cacheUnresolved(
1413 AuthoritativeNameServer server, AuthoritativeDnsServerCache authoritativeCache, EventLoop loop) {
1414
1415 server.address = InetSocketAddress.createUnresolved(
1416 server.nsName, DefaultDnsServerAddressStreamProvider.DNS_PORT);
1417
1418
1419 cache(server, authoritativeCache, loop);
1420 }
1421
1422 private static void cache(AuthoritativeNameServer server, AuthoritativeDnsServerCache cache, EventLoop loop) {
1423
1424 if (!server.isRootServer()) {
1425 cache.cache(server.domainName, server.address, server.ttl, loop);
1426 }
1427 }
1428
1429
1430
1431
1432 boolean isEmpty() {
1433 return nameServerCount == 0;
1434 }
1435
1436
1437
1438
1439 List<InetSocketAddress> addressList() {
1440 List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>(nameServerCount);
1441
1442 AuthoritativeNameServer server = head;
1443 while (server != null) {
1444 if (server.address != null) {
1445 addressList.add(server.address);
1446 }
1447 server = server.next;
1448 }
1449 return addressList;
1450 }
1451 }
1452
1453 private static final class AuthoritativeNameServer {
1454 private final int dots;
1455 private final String domainName;
1456 final boolean isCopy;
1457 final String nsName;
1458
1459 private long ttl;
1460 private InetSocketAddress address;
1461
1462 AuthoritativeNameServer next;
1463
1464 AuthoritativeNameServer(int dots, long ttl, String domainName, String nsName) {
1465 this.dots = dots;
1466 this.ttl = ttl;
1467 this.nsName = nsName;
1468 this.domainName = domainName;
1469 isCopy = false;
1470 }
1471
1472 AuthoritativeNameServer(AuthoritativeNameServer server) {
1473 dots = server.dots;
1474 ttl = server.ttl;
1475 nsName = server.nsName;
1476 domainName = server.domainName;
1477 isCopy = true;
1478 }
1479
1480
1481
1482
1483 boolean isRootServer() {
1484 return dots == 1;
1485 }
1486
1487
1488
1489
1490 void update(InetSocketAddress address, long ttl) {
1491 assert this.address == null || this.address.isUnresolved();
1492 this.address = address;
1493 this.ttl = min(this.ttl, ttl);
1494 }
1495
1496 void update(InetSocketAddress address) {
1497 update(address, Long.MAX_VALUE);
1498 }
1499 }
1500 }