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