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