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