1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutputStream;
27 import java.net.InetAddress;
28 import java.net.UnknownHostException;
29 import java.util.AbstractList;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.concurrent.ConcurrentMap;
33
34 import static io.netty.util.internal.ObjectUtil.checkNotNull;
35 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
36
37
38
39
40
41 public class DefaultDnsCache implements DnsCache {
42
43 private final Cache<DefaultDnsCacheEntry> resolveCache = new Cache<DefaultDnsCacheEntry>() {
44
45 @Override
46 protected boolean shouldReplaceAll(DefaultDnsCacheEntry entry) {
47 return entry.cause() != null;
48 }
49
50 @Override
51 protected boolean equals(DefaultDnsCacheEntry entry, DefaultDnsCacheEntry otherEntry) {
52 if (entry.address() != null) {
53 return entry.address().equals(otherEntry.address());
54 }
55 if (otherEntry.address() != null) {
56 return false;
57 }
58 return entry.cause().equals(otherEntry.cause());
59 }
60 };
61
62 private final int minTtl;
63 private final int maxTtl;
64 private final int negativeTtl;
65
66
67
68
69
70 public DefaultDnsCache() {
71 this(0, Cache.MAX_SUPPORTED_TTL_SECS, 0);
72 }
73
74
75
76
77
78
79
80 public DefaultDnsCache(int minTtl, int maxTtl, int negativeTtl) {
81 this.minTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl"));
82 this.maxTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(maxTtl, "maxTtl"));
83 if (minTtl > maxTtl) {
84 throw new IllegalArgumentException(
85 "minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
86 }
87 this.negativeTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(negativeTtl, "negativeTtl"));
88 }
89
90
91
92
93
94
95 public int minTtl() {
96 return minTtl;
97 }
98
99
100
101
102
103
104 public int maxTtl() {
105 return maxTtl;
106 }
107
108
109
110
111
112 public int negativeTtl() {
113 return negativeTtl;
114 }
115
116 @Override
117 public void clear() {
118 resolveCache.clear();
119 }
120
121 @Override
122 public boolean clear(String hostname) {
123 checkNotNull(hostname, "hostname");
124 return resolveCache.clear(appendDot(hostname));
125 }
126
127 private static boolean emptyAdditionals(DnsRecord[] additionals) {
128 return additionals == null || additionals.length == 0;
129 }
130
131 @Override
132 public List<? extends DnsCacheEntry> get(String hostname, DnsRecord[] additionals) {
133 checkNotNull(hostname, "hostname");
134 if (!emptyAdditionals(additionals)) {
135 return Collections.<DnsCacheEntry>emptyList();
136 }
137
138 final List<? extends DnsCacheEntry> entries = resolveCache.get(appendDot(hostname));
139 if (entries == null || entries.isEmpty()) {
140 return entries;
141 }
142 return new DnsCacheEntryList(entries);
143 }
144
145 @Override
146 public DnsCacheEntry cache(String hostname, DnsRecord[] additionals,
147 InetAddress address, long originalTtl, EventLoop loop) {
148 checkNotNull(hostname, "hostname");
149 checkNotNull(address, "address");
150 checkNotNull(loop, "loop");
151 DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, address);
152 if (maxTtl == 0 || !emptyAdditionals(additionals)) {
153 return e;
154 }
155 resolveCache.cache(appendDot(hostname), e, Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop);
156 return e;
157 }
158
159 @Override
160 public DnsCacheEntry cache(String hostname, DnsRecord[] additionals, Throwable cause, EventLoop loop) {
161 checkNotNull(hostname, "hostname");
162 checkNotNull(cause, "cause");
163 checkNotNull(loop, "loop");
164
165 DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, cause);
166 if (negativeTtl == 0 || !emptyAdditionals(additionals)) {
167 return e;
168 }
169
170 resolveCache.cache(appendDot(hostname), e, negativeTtl, loop);
171 return e;
172 }
173
174 @Override
175 public String toString() {
176 return new StringBuilder()
177 .append("DefaultDnsCache(minTtl=")
178 .append(minTtl).append(", maxTtl=")
179 .append(maxTtl).append(", negativeTtl=")
180 .append(negativeTtl).append(", cached resolved hostname=")
181 .append(resolveCache.size()).append(')')
182 .toString();
183 }
184
185 private static final class DefaultDnsCacheEntry implements DnsCacheEntry {
186 private final String hostname;
187 private final InetAddress address;
188 private final Throwable cause;
189 private final int hash;
190
191 DefaultDnsCacheEntry(String hostname, InetAddress address) {
192 this.hostname = hostname;
193 this.address = address;
194 cause = null;
195 hash = System.identityHashCode(this);
196 }
197
198 DefaultDnsCacheEntry(String hostname, Throwable cause) {
199 this.hostname = hostname;
200 this.cause = cause;
201 address = null;
202 hash = System.identityHashCode(this);
203 }
204
205 private DefaultDnsCacheEntry(DefaultDnsCacheEntry entry) {
206 this.hostname = entry.hostname;
207 if (entry.cause == null) {
208 this.address = entry.address;
209 this.cause = null;
210 } else {
211 this.address = null;
212 this.cause = copyThrowable(entry.cause);
213 }
214 this.hash = entry.hash;
215 }
216
217 @Override
218 public InetAddress address() {
219 return address;
220 }
221
222 @Override
223 public Throwable cause() {
224 return cause;
225 }
226
227 String hostname() {
228 return hostname;
229 }
230
231 @Override
232 public String toString() {
233 if (cause != null) {
234 return hostname + '/' + cause;
235 } else {
236 return address.toString();
237 }
238 }
239
240 @Override
241 public int hashCode() {
242 return hash;
243 }
244
245 @Override
246 public boolean equals(Object obj) {
247 return (obj instanceof DefaultDnsCacheEntry) && ((DefaultDnsCacheEntry) obj).hash == hash;
248 }
249
250 DnsCacheEntry copyIfNeeded() {
251 if (cause == null) {
252 return this;
253 }
254 return new DefaultDnsCacheEntry(this);
255 }
256 }
257
258 private static String appendDot(String hostname) {
259 return StringUtil.endsWith(hostname, '.') ? hostname : hostname + '.';
260 }
261
262 private static Throwable copyThrowable(Throwable error) {
263 if (error.getClass() == UnknownHostException.class) {
264
265 UnknownHostException copy = new UnknownHostException(error.getMessage()) {
266 @Override
267 public Throwable fillInStackTrace() {
268
269 return this;
270 }
271 };
272 copy.initCause(error.getCause());
273 copy.setStackTrace(error.getStackTrace());
274 return copy;
275 }
276 ObjectOutputStream oos = null;
277 ObjectInputStream ois = null;
278
279 try {
280
281 ByteArrayOutputStream baos = new ByteArrayOutputStream();
282 oos = new ObjectOutputStream(baos);
283 oos.writeObject(error);
284
285 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
286 ois = new ObjectInputStream(bais);
287 return (Throwable) ois.readObject();
288 } catch (IOException e) {
289 throw new IllegalStateException(e);
290 } catch (ClassNotFoundException e) {
291 throw new IllegalStateException(e);
292 } finally {
293 if (oos != null) {
294 try {
295 oos.close();
296 } catch (IOException ignore) {
297
298 }
299 }
300 if (ois != null) {
301 try {
302 ois.close();
303 } catch (IOException ignore) {
304
305 }
306 }
307 }
308 }
309
310 private static final class DnsCacheEntryList extends AbstractList<DnsCacheEntry> {
311 private final List<? extends DnsCacheEntry> entries;
312
313 DnsCacheEntryList(List<? extends DnsCacheEntry> entries) {
314 this.entries = entries;
315 }
316
317 @Override
318 public DnsCacheEntry get(int index) {
319 DefaultDnsCacheEntry entry = (DefaultDnsCacheEntry) entries.get(index);
320
321
322
323 return entry.copyIfNeeded();
324 }
325
326 @Override
327 public int size() {
328 return entries.size();
329 }
330
331 @Override
332 public int hashCode() {
333
334 return super.hashCode();
335 }
336
337 @Override
338 public boolean equals(Object o) {
339 if (o instanceof DnsCacheEntryList) {
340
341 return entries.equals(((DnsCacheEntryList) o).entries);
342 }
343 return super.equals(o);
344 }
345 };
346 }