package com.alibaba.dcm.internal;
import com.alibaba.dcm.DnsCache;
import com.alibaba.dcm.DnsCacheEntry;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import sun.net.InetAddressCachePolicy;
/**
* Util class to manipulate dns cache {@link InetAddress.Cache#cache} in {@link InetAddress#addressCache}.
* <p/>
* <b>Caution</b>: <br>
* Manipulation on {@link InetAddress#addressCache} <strong>MUST</strong>
* be guarded by {@link InetAddress#addressCache} to avoid multithreaded problem,
* you can see the implementation of {@link InetAddress} to confirm this
* (<b><i>See Also</i></b> lists key code of {@link InetAddress} related to this point).
*
* @author Jerry Lee (oldratlee at gmail dot com)
* @see InetAddress
* @see InetAddress#addressCache
* @see InetAddress.CacheEntry
* @see InetAddress#cacheInitIfNeeded()
* @see InetAddress#cacheAddresses(String, InetAddress[], boolean)
*/
public class InetAddressCacheUtil {
/**
* Need convert host to lowercase, see {@link InetAddress#cacheAddresses(String, InetAddress[], boolean)}.
*/
public static void setInetAddressCache(String host, String[] ips, long expiration)
throws NoSuchMethodException, UnknownHostException,
IllegalAccessException, InstantiationException, InvocationTargetException,
ClassNotFoundException, NoSuchFieldException {
host = host.toLowerCase();
Object entry = newCacheEntry(host, ips, expiration);
synchronized (getAddressCacheFieldOfInetAddress()) {
getCacheFiledOfAddressCacheFiledOfInetAddress().put(host, entry);
getCacheFiledOfNegativeCacheFiledOfInetAddress().remove(host);
}
}
public static void removeInetAddressCache(String host)
throws NoSuchFieldException, IllegalAccessException {
host = host.toLowerCase();
synchronized (getAddressCacheFieldOfInetAddress()) {
getCacheFiledOfAddressCacheFiledOfInetAddress().remove(host);
getCacheFiledOfNegativeCacheFiledOfInetAddress().remove(host);
}
}
static Object newCacheEntry(String host, String[] ips, long expiration)
throws UnknownHostException, ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
String className = "java.net.InetAddress$CacheEntry";
Class<?> clazz = Class.forName(className);
// InetAddress.CacheEntry has only a constructor:
// - for jdk 6, constructor signature is CacheEntry(Object address, long expiration)
// - for jdk 7+, constructor signature is CacheEntry(InetAddress[] addresses, long expiration)
// code in jdk 6:
// http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/java/net/InetAddress.java#InetAddress.CacheEntry
// code in jdk 7:
// http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/net/InetAddress.java#InetAddress.CacheEntry
// code in jdk 8:
// http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/net/InetAddress.java#InetAddress.CacheEntry
Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
return constructor.newInstance(toInetAddressArray(host, ips), expiration);
}
/**
* @return {@link InetAddress.Cache#cache} in {@link InetAddress#addressCache}
*/
@GuardedBy("getAddressCacheFieldOfInetAddress()")
static Map<String, Object> getCacheFiledOfAddressCacheFiledOfInetAddress()
throws NoSuchFieldException, IllegalAccessException {
return getCacheFiledOfInetAddress$Cache0(getAddressCacheFieldOfInetAddress());
}
/**
* @return {@link InetAddress.Cache#cache} in {@link InetAddress#negativeCache}
*/
@GuardedBy("getAddressCacheFieldOfInetAddress()")
static Map<String, Object> getCacheFiledOfNegativeCacheFiledOfInetAddress()
throws NoSuchFieldException, IllegalAccessException {
return getCacheFiledOfInetAddress$Cache0(getNegativeCacheFieldOfInetAddress());
}
@SuppressWarnings("unchecked")
static Map<String, Object> getCacheFiledOfInetAddress$Cache0(Object inetAddressCache)
throws NoSuchFieldException, IllegalAccessException {
Class clazz = inetAddressCache.getClass();
final Field cacheMapField = clazz.getDeclaredField("cache");
cacheMapField.setAccessible(true);
return (Map<String, Object>) cacheMapField.get(inetAddressCache);
}
/**
* @return {@link InetAddress#addressCache}
*/
static Object getAddressCacheFieldOfInetAddress()
throws NoSuchFieldException, IllegalAccessException {
return getAddressCacheFieldsOfInetAddress0()[0];
}
/**
* @return {@link InetAddress#negativeCache}
*/
static Object getNegativeCacheFieldOfInetAddress()
throws NoSuchFieldException, IllegalAccessException {
return getAddressCacheFieldsOfInetAddress0()[1];
}
static volatile Object[] ADDRESS_CACHE_AND_NEGATIVE_CACHE = null;
/**
* @return {@link InetAddress#addressCache} and {@link InetAddress#negativeCache}
*/
static Object[] getAddressCacheFieldsOfInetAddress0()
throws NoSuchFieldException, IllegalAccessException {
if (ADDRESS_CACHE_AND_NEGATIVE_CACHE == null) {
synchronized (InetAddressCacheUtil.class) {
if (ADDRESS_CACHE_AND_NEGATIVE_CACHE == null) { // double check
final Field cacheField = InetAddress.class.getDeclaredField("addressCache");
cacheField.setAccessible(true);
final Field negativeCacheField = InetAddress.class.getDeclaredField("negativeCache");
negativeCacheField.setAccessible(true);
ADDRESS_CACHE_AND_NEGATIVE_CACHE = new Object[]{
cacheField.get(InetAddress.class),
negativeCacheField.get(InetAddress.class)
};
}
}
}
return ADDRESS_CACHE_AND_NEGATIVE_CACHE;
}
static InetAddress[] toInetAddressArray(String host, String[] ips) throws UnknownHostException {
InetAddress[] addresses = new InetAddress[ips.length];
for (int i = 0; i < addresses.length; i++) {
addresses[i] = InetAddress.getByAddress(host, IpParserUtil.ip2ByteArray(ips[i]));
}
return addresses;
}
@Nullable
public static DnsCacheEntry getInetAddressCache(String host)
throws NoSuchFieldException, IllegalAccessException {
host = host.toLowerCase();
final Object cacheEntry;
synchronized (getAddressCacheFieldOfInetAddress()) {
cacheEntry = getCacheFiledOfAddressCacheFiledOfInetAddress().get(host);
}
if (null == cacheEntry) return null;
final DnsCacheEntry dnsCacheEntry = inetAddress$CacheEntry2DnsCacheEntry(host, cacheEntry);
if (isDnsCacheEntryExpired(dnsCacheEntry.getHost())) return null;
return dnsCacheEntry;
}
static boolean isDnsCacheEntryExpired(String host) {
return null == host || "0.0.0.0".equals(host);
}
public static DnsCache listInetAddressCache()
throws NoSuchFieldException, IllegalAccessException {
final Map<String, Object> cache;
final Map<String, Object> negativeCache;
synchronized (getAddressCacheFieldOfInetAddress()) {
cache = new HashMap<String, Object>(getCacheFiledOfAddressCacheFiledOfInetAddress());
negativeCache = new HashMap<String, Object>(getCacheFiledOfNegativeCacheFiledOfInetAddress());
}
List<DnsCacheEntry> retCache = new ArrayList<DnsCacheEntry>();
for (Map.Entry<String, Object> entry : cache.entrySet()) {
final String host = entry.getKey();
if (isDnsCacheEntryExpired(host)) { // exclude expired entries!
continue;
}
retCache.add(inetAddress$CacheEntry2DnsCacheEntry(host, entry.getValue()));
}
List<DnsCacheEntry> retNegativeCache = new ArrayList<DnsCacheEntry>();
for (Map.Entry<String, Object> entry : negativeCache.entrySet()) {
final String host = entry.getKey();
retNegativeCache.add(inetAddress$CacheEntry2DnsCacheEntry(host, entry.getValue()));
}
return new DnsCache(retCache, retNegativeCache);
}
static volatile Field expirationFieldOfInetAddress$CacheEntry = null;
static volatile Field addressesFieldOfInetAddress$CacheEntry = null;
static DnsCacheEntry inetAddress$CacheEntry2DnsCacheEntry(String host, Object entry)
throws NoSuchFieldException, IllegalAccessException {
if (expirationFieldOfInetAddress$CacheEntry == null || addressesFieldOfInetAddress$CacheEntry == null) {
synchronized (InetAddressCacheUtil.class) {
if (expirationFieldOfInetAddress$CacheEntry == null) { // double check
Class<?> cacheEntryClass = entry.getClass();
// InetAddress.CacheEntry has 2 filed:
// - for jdk 6, address and expiration
// - for jdk 7+, addresses(*renamed* from 6!) and expiration
// code in jdk 6:
// http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/java/net/InetAddress.java#InetAddress.CacheEntry
// code in jdk 7:
// http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/net/InetAddress.java#InetAddress.CacheEntry
// code in jdk 8:
// http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/net/InetAddress.java#InetAddress.CacheEntry
final Field[] fields = cacheEntryClass.getDeclaredFields();
for (Field field : fields) {
final String name = field.getName();
if (name.equals("expiration")) {
field.setAccessible(true);
expirationFieldOfInetAddress$CacheEntry = field;
} else if (name.startsWith("address")) { // use startWith so works for jdk 6 and jdk 7+
field.setAccessible(true);
addressesFieldOfInetAddress$CacheEntry = field;
} else {
throw new IllegalStateException("JDK add new Field " + name +
" for class InetAddress.CacheEntry, report bug for dns-cache-manipulator lib!");
}
}
}
}
}
long expiration = (Long) expirationFieldOfInetAddress$CacheEntry.get(entry);
InetAddress[] addresses = (InetAddress[]) addressesFieldOfInetAddress$CacheEntry.get(entry);
String[] ips = new String[addresses.length];
for (int i = 0; i < addresses.length; i++) {
ips[i] = addresses[i].getHostAddress();
}
return new DnsCacheEntry(host, ips, new Date(expiration));
}
public static void clearInetAddressCache() throws NoSuchFieldException, IllegalAccessException {
synchronized (getAddressCacheFieldOfInetAddress()) {
getCacheFiledOfAddressCacheFiledOfInetAddress().clear();
getCacheFiledOfNegativeCacheFiledOfInetAddress().clear();
}
}
/**
* Set JVM DNS cache policy
*
* @param cacheSeconds set default dns cache time. Special input case:
* <ul>
* <li> {@code -1} means never expired.(In effect, all negative value)</li>
* <li> {@code 0} never cached.</li>
* </ul>
* @see InetAddressCachePolicy
* @see InetAddressCachePolicy#cachePolicy
*/
public static void setDnsCachePolicy(int cacheSeconds)
throws NoSuchFieldException, IllegalAccessException {
setCachePolicy0(false, cacheSeconds);
}
public static int getDnsCachePolicy()
throws NoSuchFieldException, IllegalAccessException {
return InetAddressCachePolicy.get();
}
/**
* Set JVM DNS negative cache policy
*
* @param negativeCacheSeconds set default dns cache time. Special input case:
* <ul>
* <li> {@code -1} means never expired.(In effect, all negative value)</li>
* <li> {@code 0} never cached.</li>
* </ul>
* @see InetAddressCachePolicy
* @see InetAddressCachePolicy#negativeCachePolicy
*/
public static void setDnsNegativeCachePolicy(int negativeCacheSeconds)
throws NoSuchFieldException, IllegalAccessException {
setCachePolicy0(true, negativeCacheSeconds);
}
public static int getDnsNegativeCachePolicy()
throws NoSuchFieldException, IllegalAccessException {
return InetAddressCachePolicy.getNegative();
}
static volatile Field setFiled$InetAddressCachePolicy = null;
static volatile Field negativeSet$InetAddressCachePolicy = null;
static void setCachePolicy0(boolean isNegative, int seconds)
throws NoSuchFieldException, IllegalAccessException {
if (seconds < 0) {
seconds = -1;
}
final Class<?> clazz = InetAddressCachePolicy.class;
final Field cachePolicyFiled = clazz.getDeclaredField(
isNegative ? "negativeCachePolicy" : "cachePolicy");
cachePolicyFiled.setAccessible(true);
final Field setField;
if (isNegative) {
if (negativeSet$InetAddressCachePolicy == null) {
synchronized (InetAddressCacheUtil.class) {
if (negativeSet$InetAddressCachePolicy == null) {
try {
negativeSet$InetAddressCachePolicy = clazz.getDeclaredField("propertyNegativeSet");
} catch (NoSuchFieldException e) {
negativeSet$InetAddressCachePolicy = clazz.getDeclaredField("negativeSet");
}
negativeSet$InetAddressCachePolicy.setAccessible(true);
}
}
}
setField = negativeSet$InetAddressCachePolicy;
} else {
if (setFiled$InetAddressCachePolicy == null) {
synchronized (InetAddressCacheUtil.class) {
if (setFiled$InetAddressCachePolicy == null) {
try {
setFiled$InetAddressCachePolicy = clazz.getDeclaredField("propertySet");
} catch (NoSuchFieldException e) {
setFiled$InetAddressCachePolicy = clazz.getDeclaredField("set");
}
setFiled$InetAddressCachePolicy.setAccessible(true);
}
}
}
setField = setFiled$InetAddressCachePolicy;
}
synchronized (InetAddressCachePolicy.class) { // static synchronized method!
cachePolicyFiled.set(null, seconds);
setField.set(null, true);
}
}
private InetAddressCacheUtil() {
}
}