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