/* * Copyright 2016 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.resolver.dns; import io.netty.channel.EventLoop; import io.netty.handler.codec.dns.DnsRecord; import io.netty.util.internal.StringUtil; import io.netty.util.internal.UnstableApi; import java.net.InetAddress; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentMap; import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; import static java.util.Objects.requireNonNull; /** * Default implementation of {@link DnsCache}, backed by a {@link ConcurrentMap}. * If any additional {@link DnsRecord} is used, no caching takes place. */ @UnstableApi public class DefaultDnsCache implements DnsCache { private final Cache resolveCache = new Cache() { @Override protected boolean shouldReplaceAll(DefaultDnsCacheEntry entry) { return entry.cause() != null; } @Override protected boolean equals(DefaultDnsCacheEntry entry, DefaultDnsCacheEntry otherEntry) { if (entry.address() != null) { return entry.address().equals(otherEntry.address()); } if (otherEntry.address() != null) { return false; } return entry.cause().equals(otherEntry.cause()); } }; private final int minTtl; private final int maxTtl; private final int negativeTtl; /** * Create a cache that respects the TTL returned by the DNS server * and doesn't cache negative responses. */ public DefaultDnsCache() { this(0, Cache.MAX_SUPPORTED_TTL_SECS, 0); } /** * Create a cache. * @param minTtl the minimum TTL * @param maxTtl the maximum TTL * @param negativeTtl the TTL for failed queries */ public DefaultDnsCache(int minTtl, int maxTtl, int negativeTtl) { this.minTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl")); this.maxTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(maxTtl, "maxTtl")); if (minTtl > maxTtl) { throw new IllegalArgumentException( "minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)"); } this.negativeTtl = checkPositiveOrZero(negativeTtl, "negativeTtl"); } /** * Returns the minimum TTL of the cached DNS resource records (in seconds). * * @see #maxTtl() */ public int minTtl() { return minTtl; } /** * Returns the maximum TTL of the cached DNS resource records (in seconds). * * @see #minTtl() */ public int maxTtl() { return maxTtl; } /** * Returns the TTL of the cache for the failed DNS queries (in seconds). The default value is {@code 0}, which * disables the cache for negative results. */ public int negativeTtl() { return negativeTtl; } @Override public void clear() { resolveCache.clear(); } @Override public boolean clear(String hostname) { requireNonNull(hostname, "hostname"); return resolveCache.clear(appendDot(hostname)); } private static boolean emptyAdditionals(DnsRecord[] additionals) { return additionals == null || additionals.length == 0; } @Override public List get(String hostname, DnsRecord[] additionals) { requireNonNull(hostname, "hostname"); if (!emptyAdditionals(additionals)) { return Collections.emptyList(); } return resolveCache.get(appendDot(hostname)); } @Override public DnsCacheEntry cache(String hostname, DnsRecord[] additionals, InetAddress address, long originalTtl, EventLoop loop) { requireNonNull(hostname, "hostname"); requireNonNull(address, "address"); requireNonNull(loop, "loop"); DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, address); if (maxTtl == 0 || !emptyAdditionals(additionals)) { return e; } resolveCache.cache(appendDot(hostname), e, Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop); return e; } @Override public DnsCacheEntry cache(String hostname, DnsRecord[] additionals, Throwable cause, EventLoop loop) { requireNonNull(hostname, "hostname"); requireNonNull(cause, "cause"); requireNonNull(loop, "loop"); DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, cause); if (negativeTtl == 0 || !emptyAdditionals(additionals)) { return e; } resolveCache.cache(appendDot(hostname), e, negativeTtl, loop); return e; } @Override public String toString() { return new StringBuilder() .append("DefaultDnsCache(minTtl=") .append(minTtl).append(", maxTtl=") .append(maxTtl).append(", negativeTtl=") .append(negativeTtl).append(", cached resolved hostname=") .append(resolveCache.size()).append(')') .toString(); } private static final class DefaultDnsCacheEntry implements DnsCacheEntry { private final String hostname; private final InetAddress address; private final Throwable cause; DefaultDnsCacheEntry(String hostname, InetAddress address) { this.hostname = hostname; this.address = address; cause = null; } DefaultDnsCacheEntry(String hostname, Throwable cause) { this.hostname = hostname; this.cause = cause; address = null; } @Override public InetAddress address() { return address; } @Override public Throwable cause() { return cause; } String hostname() { return hostname; } @Override public String toString() { if (cause != null) { return hostname + '/' + cause; } else { return address.toString(); } } } private static String appendDot(String hostname) { return StringUtil.endsWith(hostname, '.') ? hostname : hostname + '.'; } }