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