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