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, buildAliasMap(envelope.content(), cnameCache(), parent.executor()),
626 queryLifecycleObserver, promise);
627 return;
628 }
629
630 for (DnsRecordType expectedType : expectedTypes) {
631 if (type == expectedType) {
632 onExpectedResponse(question, envelope, queryLifecycleObserver, promise);
633 return;
634 }
635 }
636
637 queryLifecycleObserver.queryFailed(UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION);
638 return;
639 }
640
641
642 if (code != DnsResponseCode.NXDOMAIN) {
643 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
644 queryLifecycleObserver.queryNoAnswer(code), true, promise, cause(code));
645 } else {
646 queryLifecycleObserver.queryFailed(NXDOMAIN_QUERY_FAILED_EXCEPTION);
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666 if (!res.isAuthoritativeAnswer()) {
667 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
668 newDnsQueryLifecycleObserver(question), true, promise, cause(code));
669 } else {
670
671 tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question,
672 queryLifecycleObserver, promise, NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION);
673 }
674 }
675 } finally {
676 ReferenceCountUtil.safeRelease(envelope);
677 }
678 }
679
680
681
682
683 private boolean handleRedirect(
684 DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
685 final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
686 final DnsResponse res = envelope.content();
687
688
689 if (res.count(DnsSection.ANSWER) == 0) {
690 AuthoritativeNameServerList serverNames = extractAuthoritativeNameServers(question.name(), res);
691 if (serverNames != null) {
692 int additionalCount = res.count(DnsSection.ADDITIONAL);
693
694 AuthoritativeDnsServerCache authoritativeDnsServerCache = authoritativeDnsServerCache();
695 for (int i = 0; i < additionalCount; i++) {
696 final DnsRecord r = res.recordAt(DnsSection.ADDITIONAL, i);
697
698 if (r.type() == DnsRecordType.A && !parent.supportsARecords() ||
699 r.type() == DnsRecordType.AAAA && !parent.supportsAAAARecords()) {
700 continue;
701 }
702
703
704
705 serverNames.handleWithAdditional(parent, r, authoritativeDnsServerCache);
706 }
707
708
709 serverNames.handleWithoutAdditionals(parent, resolveCache(), authoritativeDnsServerCache);
710
711 List<InetSocketAddress> addresses = serverNames.addressList();
712
713
714 DnsServerAddressStream serverStream = parent.newRedirectDnsServerStream(
715 question.name(), addresses);
716
717 if (serverStream != null) {
718 query(serverStream, 0, question,
719 queryLifecycleObserver.queryRedirected(new DnsAddressStreamList(serverStream)),
720 true, promise, null);
721 return true;
722 }
723 }
724 }
725 return false;
726 }
727
728 private static Throwable cause(final DnsResponseCode code) {
729 assert code != null;
730 if (SERVFAIL.intValue() == code.intValue()) {
731 return SERVFAIL_QUERY_FAILED_EXCEPTION;
732 } else if (NXDOMAIN.intValue() == code.intValue()) {
733 return NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION;
734 }
735
736 return null;
737 }
738
739 private static final class DnsAddressStreamList extends AbstractList<InetSocketAddress> {
740
741 private final DnsServerAddressStream duplicate;
742 private List<InetSocketAddress> addresses;
743
744 DnsAddressStreamList(DnsServerAddressStream stream) {
745 duplicate = stream.duplicate();
746 }
747
748 @Override
749 public InetSocketAddress get(int index) {
750 if (addresses == null) {
751 DnsServerAddressStream stream = duplicate.duplicate();
752 addresses = new ArrayList<InetSocketAddress>(size());
753 for (int i = 0; i < stream.size(); i++) {
754 addresses.add(stream.next());
755 }
756 }
757 return addresses.get(index);
758 }
759
760 @Override
761 public int size() {
762 return duplicate.size();
763 }
764
765 @Override
766 public Iterator<InetSocketAddress> iterator() {
767 return new Iterator<InetSocketAddress>() {
768 private final DnsServerAddressStream stream = duplicate.duplicate();
769 private int i;
770
771 @Override
772 public boolean hasNext() {
773 return i < stream.size();
774 }
775
776 @Override
777 public InetSocketAddress next() {
778 if (!hasNext()) {
779 throw new NoSuchElementException();
780 }
781 i++;
782 return stream.next();
783 }
784
785 @Override
786 public void remove() {
787 throw new UnsupportedOperationException();
788 }
789 };
790 }
791 }
792
793
794
795
796
797 private static AuthoritativeNameServerList extractAuthoritativeNameServers(String questionName, DnsResponse res) {
798 int authorityCount = res.count(DnsSection.AUTHORITY);
799 if (authorityCount == 0) {
800 return null;
801 }
802
803 AuthoritativeNameServerList serverNames = new AuthoritativeNameServerList(questionName);
804 for (int i = 0; i < authorityCount; i++) {
805 serverNames.add(res.recordAt(DnsSection.AUTHORITY, i));
806 }
807 return serverNames.isEmpty() ? null : serverNames;
808 }
809
810 private void onExpectedResponse(
811 DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
812 final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
813
814
815 final DnsResponse response = envelope.content();
816 final Map<String, String> cnames = buildAliasMap(response, cnameCache(), parent.executor());
817 final int answerCount = response.count(DnsSection.ANSWER);
818
819 boolean found = false;
820 boolean completeEarly = this.completeEarly;
821 boolean cnameNeedsFollow = !cnames.isEmpty();
822 for (int i = 0; i < answerCount; i ++) {
823 final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
824 final DnsRecordType type = r.type();
825 boolean matches = false;
826 for (DnsRecordType expectedType : expectedTypes) {
827 if (type == expectedType) {
828 matches = true;
829 break;
830 }
831 }
832
833 if (!matches) {
834 continue;
835 }
836
837 final String questionName = question.name().toLowerCase(Locale.US);
838 final String recordName = r.name().toLowerCase(Locale.US);
839
840
841 if (!recordName.equals(questionName)) {
842 Map<String, String> cnamesCopy = new HashMap<String, String>(cnames);
843
844 String resolved = questionName;
845 do {
846 resolved = cnamesCopy.remove(resolved);
847 if (recordName.equals(resolved)) {
848
849 cnameNeedsFollow = false;
850 break;
851 }
852 } while (resolved != null);
853
854 if (resolved == null) {
855 assert questionName.isEmpty() || questionName.charAt(questionName.length() - 1) == '.';
856
857 for (String searchDomain : parent.searchDomains()) {
858 if (searchDomain.isEmpty()) {
859 continue;
860 }
861
862 final String fqdn;
863 if (searchDomain.charAt(searchDomain.length() - 1) == '.') {
864 fqdn = questionName + searchDomain;
865 } else {
866 fqdn = questionName + searchDomain + '.';
867 }
868 if (recordName.equals(fqdn)) {
869 resolved = recordName;
870 break;
871 }
872 }
873 if (resolved == null) {
874 if (logger.isDebugEnabled()) {
875 logger.debug("{} Ignoring record {} for [{}: {}] as it contains a different name than " +
876 "the question name [{}]. Cnames: {}, Search domains: {}",
877 channel, r.toString(), response.id(), envelope.sender(),
878 questionName, cnames, parent.searchDomains());
879 }
880 continue;
881 }
882 }
883 }
884
885 final T converted = convertRecord(r, hostname, additionals, parent.executor());
886 if (converted == null) {
887 if (logger.isDebugEnabled()) {
888 logger.debug("{} Ignoring record {} for [{}: {}] as the converted record is null. "
889 + "Hostname [{}], Additionals: {}",
890 channel, r.toString(), response.id(),
891 envelope.sender(), hostname, additionals);
892 }
893 continue;
894 }
895
896 boolean shouldRelease = false;
897
898
899 if (!completeEarly) {
900 completeEarly = isCompleteEarly(converted);
901 }
902
903
904
905 if (!promise.isDone()) {
906
907
908
909
910
911 if (finalResult == null) {
912 finalResult = new ArrayList<T>(8);
913 finalResult.add(converted);
914 } else if (isDuplicateAllowed() || !finalResult.contains(converted)) {
915 finalResult.add(converted);
916 } else {
917 shouldRelease = true;
918 }
919 } else {
920 shouldRelease = true;
921 }
922
923 cache(hostname, additionals, r, converted);
924 found = true;
925
926 if (shouldRelease) {
927 ReferenceCountUtil.release(converted);
928 }
929
930 }
931
932 if (found && !cnameNeedsFollow) {
933
934
935 if (completeEarly) {
936 this.completeEarly = true;
937 }
938 queryLifecycleObserver.querySucceed();
939 } else if (cnames.isEmpty()) {
940 queryLifecycleObserver.queryFailed(NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION);
941 } else {
942 queryLifecycleObserver.querySucceed();
943
944 onResponseCNAME(question, cnames, newDnsQueryLifecycleObserver(question), promise);
945 }
946 }
947
948 private void onResponseCNAME(
949 DnsQuestion question, Map<String, String> cnames,
950 final DnsQueryLifecycleObserver queryLifecycleObserver,
951 Promise<List<T>> promise) {
952
953
954 String resolved = question.name().toLowerCase(Locale.US);
955 boolean found = false;
956 while (!cnames.isEmpty()) {
957
958
959 final String next = cnames.remove(resolved);
960 if (next != null) {
961 found = true;
962 resolved = next;
963 } else {
964 break;
965 }
966 }
967
968 if (found) {
969 followCname(question, resolved, queryLifecycleObserver, promise);
970 } else {
971 queryLifecycleObserver.queryFailed(CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION);
972 }
973 }
974
975 private static Map<String, String> buildAliasMap(DnsResponse response, DnsCnameCache cache, EventLoop loop) {
976 final int answerCount = response.count(DnsSection.ANSWER);
977 Map<String, String> cnames = null;
978 for (int i = 0; i < answerCount; i ++) {
979 final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
980 final DnsRecordType type = r.type();
981 if (type != DnsRecordType.CNAME) {
982 continue;
983 }
984
985 if (!(r instanceof DnsRawRecord)) {
986 continue;
987 }
988
989 final ByteBuf recordContent = ((ByteBufHolder) r).content();
990 final String domainName = decodeDomainName(recordContent);
991 if (domainName == null) {
992 continue;
993 }
994
995 if (cnames == null) {
996 cnames = new HashMap<String, String>(min(8, answerCount));
997 }
998
999 String name = r.name().toLowerCase(Locale.US);
1000 String mapping = domainName.toLowerCase(Locale.US);
1001
1002
1003 String nameWithDot = hostnameWithDot(name);
1004 String mappingWithDot = hostnameWithDot(mapping);
1005 if (!nameWithDot.equalsIgnoreCase(mappingWithDot)) {
1006 cache.cache(nameWithDot, mappingWithDot, r.timeToLive(), loop);
1007 cnames.put(name, mapping);
1008 }
1009 }
1010
1011 return cnames != null? cnames : Collections.<String, String>emptyMap();
1012 }
1013
1014 private void tryToFinishResolve(final DnsServerAddressStream nameServerAddrStream,
1015 final int nameServerAddrStreamIndex,
1016 final DnsQuestion question,
1017 final DnsQueryLifecycleObserver queryLifecycleObserver,
1018 final Promise<List<T>> promise,
1019 final Throwable cause) {
1020
1021
1022 if (!completeEarly && !queriesInProgress.isEmpty()) {
1023 queryLifecycleObserver.queryCancelled(allowedQueries);
1024
1025
1026
1027 return;
1028 }
1029
1030
1031 if (finalResult == null) {
1032 if (nameServerAddrStreamIndex < nameServerAddrStream.size()) {
1033 if (queryLifecycleObserver == NoopDnsQueryLifecycleObserver.INSTANCE) {
1034
1035
1036 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
1037 newDnsQueryLifecycleObserver(question), true, promise, cause);
1038 } else {
1039 query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, queryLifecycleObserver,
1040 true, promise, cause);
1041 }
1042 return;
1043 }
1044
1045 queryLifecycleObserver.queryFailed(NAME_SERVERS_EXHAUSTED_EXCEPTION);
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058 if (TRY_FINAL_CNAME_ON_ADDRESS_LOOKUPS) {
1059
1060
1061
1062 final boolean isValidResponse =
1063 cause == NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION || cause == SERVFAIL_QUERY_FAILED_EXCEPTION;
1064 if ((cause == null || isValidResponse) && !triedCNAME &&
1065 (question.type() == DnsRecordType.A || question.type() == DnsRecordType.AAAA)) {
1066
1067 triedCNAME = true;
1068
1069 query(hostname, DnsRecordType.CNAME, getNameServers(hostname), true, promise);
1070 return;
1071 }
1072 }
1073 } else {
1074 queryLifecycleObserver.queryCancelled(allowedQueries);
1075 }
1076
1077
1078 finishResolve(promise, cause);
1079 }
1080
1081 private void finishResolve(Promise<List<T>> promise, Throwable cause) {
1082
1083
1084 if (!completeEarly && !queriesInProgress.isEmpty()) {
1085
1086 for (Iterator<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> i = queriesInProgress.iterator();
1087 i.hasNext();) {
1088 Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = i.next();
1089 i.remove();
1090
1091 f.cancel(false);
1092 }
1093 }
1094
1095 if (finalResult != null) {
1096 if (!promise.isDone()) {
1097
1098 final List<T> result = filterResults(finalResult);
1099
1100 finalResult = Collections.emptyList();
1101 if (!DnsNameResolver.trySuccess(promise, result)) {
1102 for (T item : result) {
1103 ReferenceCountUtil.safeRelease(item);
1104 }
1105 }
1106 } else {
1107
1108
1109 assert finalResult.isEmpty();
1110 }
1111 return;
1112 }
1113
1114
1115 final int maxAllowedQueries = parent.maxQueriesPerResolve();
1116 final int tries = maxAllowedQueries - allowedQueries;
1117 final StringBuilder buf = new StringBuilder(64);
1118
1119 buf.append("Failed to resolve '").append(hostname).append("' ").append(Arrays.toString(expectedTypes));
1120 if (tries > 1) {
1121 if (tries < maxAllowedQueries) {
1122 buf.append(" after ")
1123 .append(tries)
1124 .append(" queries ");
1125 } else {
1126 buf.append(". Exceeded max queries per resolve ")
1127 .append(maxAllowedQueries)
1128 .append(' ');
1129 }
1130 }
1131 final UnknownHostException unknownHostException = new UnknownHostException(buf.toString());
1132 if (cause == null) {
1133
1134
1135 cache(hostname, additionals, unknownHostException);
1136 } else {
1137 unknownHostException.initCause(cause);
1138 }
1139 promise.tryFailure(unknownHostException);
1140 }
1141
1142 static String decodeDomainName(ByteBuf in) {
1143 in.markReaderIndex();
1144 try {
1145 return DefaultDnsRecordDecoder.decodeName(in);
1146 } catch (CorruptedFrameException e) {
1147
1148 return null;
1149 } finally {
1150 in.resetReaderIndex();
1151 }
1152 }
1153
1154 private DnsServerAddressStream getNameServers(String name) {
1155 DnsServerAddressStream stream = getNameServersFromCache(name);
1156 if (stream == null) {
1157
1158
1159
1160 if (name.equals(hostname)) {
1161 return nameServerAddrs.duplicate();
1162 }
1163 return parent.newNameServerAddressStream(name);
1164 }
1165 return stream;
1166 }
1167
1168 private void followCname(DnsQuestion question, String cname, DnsQueryLifecycleObserver queryLifecycleObserver,
1169 Promise<List<T>> promise) {
1170 final DnsQuestion cnameQuestion;
1171 final DnsServerAddressStream stream;
1172 try {
1173 cname = cnameResolveFromCache(cnameCache(), cname);
1174 stream = getNameServers(cname);
1175 cnameQuestion = new DefaultDnsQuestion(cname, question.type(), dnsClass);
1176 } catch (Throwable cause) {
1177 queryLifecycleObserver.queryFailed(cause);
1178 PlatformDependent.throwException(cause);
1179 return;
1180 }
1181 query(stream, 0, cnameQuestion, queryLifecycleObserver.queryCNAMEd(cnameQuestion),
1182 true, promise, null);
1183 }
1184
1185 private boolean query(String hostname, DnsRecordType type, DnsServerAddressStream dnsServerAddressStream,
1186 boolean flush, Promise<List<T>> promise) {
1187 final DnsQuestion question;
1188 try {
1189 question = new DefaultDnsQuestion(hostname, type, dnsClass);
1190 } catch (Throwable cause) {
1191
1192
1193 promise.tryFailure(new IllegalArgumentException("Unable to create DNS Question for: [" + hostname + ", " +
1194 type + ']', cause));
1195 return false;
1196 }
1197 query(dnsServerAddressStream, 0, question, newDnsQueryLifecycleObserver(question), flush, promise, null);
1198 return true;
1199 }
1200
1201 private DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsQuestion question) {
1202 return parent.dnsQueryLifecycleObserverFactory().newDnsQueryLifecycleObserver(question);
1203 }
1204
1205 private final class CombinedDnsServerAddressStream implements DnsServerAddressStream {
1206 private final InetSocketAddress replaced;
1207 private final DnsServerAddressStream originalStream;
1208 private final List<InetAddress> resolvedAddresses;
1209 private Iterator<InetAddress> resolved;
1210
1211 CombinedDnsServerAddressStream(InetSocketAddress replaced, List<InetAddress> resolvedAddresses,
1212 DnsServerAddressStream originalStream) {
1213 this.replaced = replaced;
1214 this.resolvedAddresses = resolvedAddresses;
1215 this.originalStream = originalStream;
1216 resolved = resolvedAddresses.iterator();
1217 }
1218
1219 @Override
1220 public InetSocketAddress next() {
1221 if (resolved.hasNext()) {
1222 return nextResolved0();
1223 }
1224 InetSocketAddress address = originalStream.next();
1225 if (address.equals(replaced)) {
1226 resolved = resolvedAddresses.iterator();
1227 return nextResolved0();
1228 }
1229 return address;
1230 }
1231
1232 private InetSocketAddress nextResolved0() {
1233 return parent.newRedirectServerAddress(resolved.next());
1234 }
1235
1236 @Override
1237 public int size() {
1238 return originalStream.size() + resolvedAddresses.size() - 1;
1239 }
1240
1241 @Override
1242 public DnsServerAddressStream duplicate() {
1243 return new CombinedDnsServerAddressStream(replaced, resolvedAddresses, originalStream.duplicate());
1244 }
1245 }
1246
1247
1248
1249
1250 private static final class AuthoritativeNameServerList {
1251
1252 private final String questionName;
1253
1254
1255 private AuthoritativeNameServer head;
1256
1257 private int nameServerCount;
1258
1259 AuthoritativeNameServerList(String questionName) {
1260 this.questionName = questionName.toLowerCase(Locale.US);
1261 }
1262
1263 void add(DnsRecord r) {
1264 if (r.type() != DnsRecordType.NS || !(r instanceof DnsRawRecord)) {
1265 return;
1266 }
1267
1268
1269 if (questionName.length() < r.name().length()) {
1270 return;
1271 }
1272
1273 String recordName = r.name().toLowerCase(Locale.US);
1274
1275 int dots = 0;
1276 for (int a = recordName.length() - 1, b = questionName.length() - 1; a >= 0; a--, b--) {
1277 char c = recordName.charAt(a);
1278 if (questionName.charAt(b) != c) {
1279 return;
1280 }
1281 if (c == '.') {
1282 dots++;
1283 }
1284 }
1285
1286 if (head != null && head.dots > dots) {
1287
1288 return;
1289 }
1290
1291 final ByteBuf recordContent = ((ByteBufHolder) r).content();
1292 final String domainName = decodeDomainName(recordContent);
1293 if (domainName == null) {
1294
1295 return;
1296 }
1297
1298
1299
1300 if (head == null || head.dots < dots) {
1301 nameServerCount = 1;
1302 head = new AuthoritativeNameServer(dots, r.timeToLive(), recordName, domainName);
1303 } else if (head.dots == dots) {
1304 AuthoritativeNameServer serverName = head;
1305 while (serverName.next != null) {
1306 serverName = serverName.next;
1307 }
1308 serverName.next = new AuthoritativeNameServer(dots, r.timeToLive(), recordName, domainName);
1309 nameServerCount++;
1310 }
1311 }
1312
1313 void handleWithAdditional(
1314 DnsNameResolver parent, DnsRecord r, AuthoritativeDnsServerCache authoritativeCache) {
1315
1316 AuthoritativeNameServer serverName = head;
1317
1318 String nsName = r.name();
1319 InetAddress resolved = decodeAddress(r, nsName, parent.isDecodeIdn());
1320 if (resolved == null) {
1321
1322 return;
1323 }
1324
1325 while (serverName != null) {
1326 if (serverName.nsName.equalsIgnoreCase(nsName)) {
1327 if (serverName.address != null) {
1328
1329
1330 while (serverName.next != null && serverName.next.isCopy) {
1331 serverName = serverName.next;
1332 }
1333 AuthoritativeNameServer server = new AuthoritativeNameServer(serverName);
1334 server.next = serverName.next;
1335 serverName.next = server;
1336 serverName = server;
1337
1338 nameServerCount++;
1339 }
1340
1341
1342 serverName.update(parent.newRedirectServerAddress(resolved), r.timeToLive());
1343
1344
1345 cache(serverName, authoritativeCache, parent.executor());
1346 return;
1347 }
1348 serverName = serverName.next;
1349 }
1350 }
1351
1352
1353 void handleWithoutAdditionals(
1354 DnsNameResolver parent, DnsCache cache, AuthoritativeDnsServerCache authoritativeCache) {
1355 AuthoritativeNameServer serverName = head;
1356
1357 while (serverName != null) {
1358 if (serverName.address == null) {
1359
1360 cacheUnresolved(serverName, authoritativeCache, parent.executor());
1361
1362
1363
1364 List<? extends DnsCacheEntry> entries = cache.get(serverName.nsName, null);
1365 if (entries != null && !entries.isEmpty()) {
1366 InetAddress address = entries.get(0).address();
1367
1368
1369 if (address != null) {
1370 serverName.update(parent.newRedirectServerAddress(address));
1371
1372 for (int i = 1; i < entries.size(); i++) {
1373 address = entries.get(i).address();
1374
1375 assert address != null :
1376 "Cache returned a cached failure, should never return anything else";
1377
1378 AuthoritativeNameServer server = new AuthoritativeNameServer(serverName);
1379 server.next = serverName.next;
1380 serverName.next = server;
1381 serverName = server;
1382 serverName.update(parent.newRedirectServerAddress(address));
1383
1384 nameServerCount++;
1385 }
1386 }
1387 }
1388 }
1389 serverName = serverName.next;
1390 }
1391 }
1392
1393 private static void cacheUnresolved(
1394 AuthoritativeNameServer server, AuthoritativeDnsServerCache authoritativeCache, EventLoop loop) {
1395
1396 server.address = InetSocketAddress.createUnresolved(
1397 server.nsName, DefaultDnsServerAddressStreamProvider.DNS_PORT);
1398
1399
1400 cache(server, authoritativeCache, loop);
1401 }
1402
1403 private static void cache(AuthoritativeNameServer server, AuthoritativeDnsServerCache cache, EventLoop loop) {
1404
1405 if (!server.isRootServer()) {
1406 cache.cache(server.domainName, server.address, server.ttl, loop);
1407 }
1408 }
1409
1410
1411
1412
1413 boolean isEmpty() {
1414 return nameServerCount == 0;
1415 }
1416
1417
1418
1419
1420 List<InetSocketAddress> addressList() {
1421 List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>(nameServerCount);
1422
1423 AuthoritativeNameServer server = head;
1424 while (server != null) {
1425 if (server.address != null) {
1426 addressList.add(server.address);
1427 }
1428 server = server.next;
1429 }
1430 return addressList;
1431 }
1432 }
1433
1434 private static final class AuthoritativeNameServer {
1435 private final int dots;
1436 private final String domainName;
1437 final boolean isCopy;
1438 final String nsName;
1439
1440 private long ttl;
1441 private InetSocketAddress address;
1442
1443 AuthoritativeNameServer next;
1444
1445 AuthoritativeNameServer(int dots, long ttl, String domainName, String nsName) {
1446 this.dots = dots;
1447 this.ttl = ttl;
1448 this.nsName = nsName;
1449 this.domainName = domainName;
1450 isCopy = false;
1451 }
1452
1453 AuthoritativeNameServer(AuthoritativeNameServer server) {
1454 dots = server.dots;
1455 ttl = server.ttl;
1456 nsName = server.nsName;
1457 domainName = server.domainName;
1458 isCopy = true;
1459 }
1460
1461
1462
1463
1464 boolean isRootServer() {
1465 return dots == 1;
1466 }
1467
1468
1469
1470
1471 void update(InetSocketAddress address, long ttl) {
1472 assert this.address == null || this.address.isUnresolved();
1473 this.address = address;
1474 this.ttl = min(this.ttl, ttl);
1475 }
1476
1477 void update(InetSocketAddress address) {
1478 update(address, Long.MAX_VALUE);
1479 }
1480 }
1481 }