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