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