View Javadoc
1   /*
2    * Copyright 2016 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.resolver.dns;
17  
18  import io.netty.channel.EventLoop;
19  import io.netty.handler.codec.dns.DnsRecord;
20  import io.netty.util.internal.StringUtil;
21  import io.netty.util.internal.UnstableApi;
22  
23  import java.net.InetAddress;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.concurrent.ConcurrentMap;
27  
28  import static io.netty.util.internal.ObjectUtil.checkNotNull;
29  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
30  
31  /**
32   * Default implementation of {@link DnsCache}, backed by a {@link ConcurrentMap}.
33   * If any additional {@link DnsRecord} is used, no caching takes place.
34   */
35  @UnstableApi
36  public class DefaultDnsCache implements DnsCache {
37  
38      private final Cache<DefaultDnsCacheEntry> resolveCache = new Cache<DefaultDnsCacheEntry>() {
39  
40          @Override
41          protected boolean shouldReplaceAll(DefaultDnsCacheEntry entry) {
42              return entry.cause() != null;
43          }
44  
45          @Override
46          protected boolean equals(DefaultDnsCacheEntry entry, DefaultDnsCacheEntry otherEntry) {
47              if (entry.address() != null) {
48                  return entry.address().equals(otherEntry.address());
49              }
50              if (otherEntry.address() != null) {
51                  return false;
52              }
53              return entry.cause().equals(otherEntry.cause());
54          }
55      };
56  
57      private final int minTtl;
58      private final int maxTtl;
59      private final int negativeTtl;
60  
61      /**
62       * Create a cache that respects the TTL returned by the DNS server
63       * and doesn't cache negative responses.
64       */
65      public DefaultDnsCache() {
66          this(0, Cache.MAX_SUPPORTED_TTL_SECS, 0);
67      }
68  
69      /**
70       * Create a cache.
71       * @param minTtl the minimum TTL
72       * @param maxTtl the maximum TTL
73       * @param negativeTtl the TTL for failed queries
74       */
75      public DefaultDnsCache(int minTtl, int maxTtl, int negativeTtl) {
76          this.minTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl"));
77          this.maxTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(maxTtl, "maxTtl"));
78          if (minTtl > maxTtl) {
79              throw new IllegalArgumentException(
80                      "minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
81          }
82          this.negativeTtl = checkPositiveOrZero(negativeTtl, "negativeTtl");
83      }
84  
85      /**
86       * Returns the minimum TTL of the cached DNS resource records (in seconds).
87       *
88       * @see #maxTtl()
89       */
90      public int minTtl() {
91          return minTtl;
92      }
93  
94      /**
95       * Returns the maximum TTL of the cached DNS resource records (in seconds).
96       *
97       * @see #minTtl()
98       */
99      public int maxTtl() {
100         return maxTtl;
101     }
102 
103     /**
104      * Returns the TTL of the cache for the failed DNS queries (in seconds). The default value is {@code 0}, which
105      * disables the cache for negative results.
106      */
107     public int negativeTtl() {
108         return negativeTtl;
109     }
110 
111     @Override
112     public void clear() {
113         resolveCache.clear();
114     }
115 
116     @Override
117     public boolean clear(String hostname) {
118         checkNotNull(hostname, "hostname");
119         return resolveCache.clear(appendDot(hostname));
120     }
121 
122     private static boolean emptyAdditionals(DnsRecord[] additionals) {
123         return additionals == null || additionals.length == 0;
124     }
125 
126     @Override
127     public List<? extends DnsCacheEntry> get(String hostname, DnsRecord[] additionals) {
128         checkNotNull(hostname, "hostname");
129         if (!emptyAdditionals(additionals)) {
130             return Collections.<DnsCacheEntry>emptyList();
131         }
132 
133         return resolveCache.get(appendDot(hostname));
134     }
135 
136     @Override
137     public DnsCacheEntry cache(String hostname, DnsRecord[] additionals,
138                                InetAddress address, long originalTtl, EventLoop loop) {
139         checkNotNull(hostname, "hostname");
140         checkNotNull(address, "address");
141         checkNotNull(loop, "loop");
142         DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, address);
143         if (maxTtl == 0 || !emptyAdditionals(additionals)) {
144             return e;
145         }
146         resolveCache.cache(appendDot(hostname), e, Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop);
147         return e;
148     }
149 
150     @Override
151     public DnsCacheEntry cache(String hostname, DnsRecord[] additionals, Throwable cause, EventLoop loop) {
152         checkNotNull(hostname, "hostname");
153         checkNotNull(cause, "cause");
154         checkNotNull(loop, "loop");
155 
156         DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, cause);
157         if (negativeTtl == 0 || !emptyAdditionals(additionals)) {
158             return e;
159         }
160 
161         resolveCache.cache(appendDot(hostname), e, negativeTtl, loop);
162         return e;
163     }
164 
165     @Override
166     public String toString() {
167         return new StringBuilder()
168                 .append("DefaultDnsCache(minTtl=")
169                 .append(minTtl).append(", maxTtl=")
170                 .append(maxTtl).append(", negativeTtl=")
171                 .append(negativeTtl).append(", cached resolved hostname=")
172                 .append(resolveCache.size()).append(')')
173                 .toString();
174     }
175 
176     private static final class DefaultDnsCacheEntry implements DnsCacheEntry {
177         private final String hostname;
178         private final InetAddress address;
179         private final Throwable cause;
180 
181         DefaultDnsCacheEntry(String hostname, InetAddress address) {
182             this.hostname = hostname;
183             this.address = address;
184             cause = null;
185         }
186 
187         DefaultDnsCacheEntry(String hostname, Throwable cause) {
188             this.hostname = hostname;
189             this.cause = cause;
190             address = null;
191         }
192 
193         @Override
194         public InetAddress address() {
195             return address;
196         }
197 
198         @Override
199         public Throwable cause() {
200             return cause;
201         }
202 
203         String hostname() {
204             return hostname;
205         }
206 
207         @Override
208         public String toString() {
209             if (cause != null) {
210                 return hostname + '/' + cause;
211             } else {
212                 return address.toString();
213             }
214         }
215     }
216 
217     private static String appendDot(String hostname) {
218         return StringUtil.endsWith(hostname, '.') ? hostname : hostname + '.';
219     }
220 }