/*- * Copyright (C) 2002, 2017, Oracle and/or its affiliates. All rights reserved. * * This file was distributed by Oracle as part of a version of Oracle Berkeley * DB Java Edition made available at: * * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html * * Please see the LICENSE file included in the top-level directory of the * appropriate version of Oracle Berkeley DB Java Edition for a copy of the * license and additional information. */ package com.sleepycat.je.rep.utilint; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.logging.Logger; import sun.net.spi.nameservice.NameService; import sun.net.spi.nameservice.NameServiceDescriptor; import com.sleepycat.je.utilint.LoggerUtils; /** * Define a JDK name service provider that can be controlled by tests to * simulate DNS failures. The idea is to define dummy DNS names that translate * to the loopback address, and then undefine them as needed. * *

To use this class, you need to make a few modifications to the JVM that * wants to use it:

* * Although the name service provider facility is undocumented, at last check * it appears to be supported by the J9 and icedtea JVM implementations. */ public class LocalAliasNameService implements NameService { static final Logger logger = LoggerUtils.getLoggerFixedPrefix(LocalAliasNameService.class, "Test"); /* Only referenced by getLocalHost */ private static InetAddress localHost = null; private static boolean computingLocalHost = false; /** * The service descriptor that defines {@code LocalAliasNameService} as a * "dns" provider named "localalias". */ public static class Descriptor implements NameServiceDescriptor { @Override public NameService createNameService() { return new LocalAliasNameService(); } @Override public String getProviderName() { return "localalias"; } @Override public String getType() { return "dns"; } } private static final Set aliases = Collections.synchronizedSet(new HashSet()); private LocalAliasNameService() { logger.info("Created LocalAliasNameService"); } /** * Add a new alias for the loopback address. * * @param alias the new alias */ public static void addAlias(String alias) { logger.info("LocalAliasNameService.addAlias: " + alias); aliases.add(alias); } /** * Remove an alias for the loopback address. * * @param alias the alias to remove */ public static void removeAlias(String alias) { logger.info("LocalAliasNameService.removeAlias: " + alias); aliases.remove(alias); } /** * Remove all aliases for the loopback address. */ public static void clearAllAliases() { logger.info("LocalAliasNameService.clearAllAliases"); aliases.clear(); } /** * Use reflection to set the internal DNS cache policy. The {@link * InetAddress} class documents security properties for controlling this * (networkaddress.cache.ttl and networkaddress.cache.negative.ttl), but * those settings only take effect when they are specified before any * address lookups are performed. The JUnit test infrastructure does * address lookups before testing starts, so the security properties are * not effective. Instead, use reflection to set the field values * directly. * *

The cache policy values specify the time in milliseconds that the * cache should remain valid, with 0 meaning don't cache and -1 meaning * cache stays valid forever. * * @param positive the cache policy for successful lookups * @param negative the cache policy for unsuccessful lookups * @return an array of the previous cache policies, with the positive cache * value appearing first */ public static int[] setDNSCachePolicy(int positive, int negative) { try { Class cachePolicyClass = Class.forName("sun.net.InetAddressCachePolicy"); Field positiveField = cachePolicyClass.getDeclaredField("cachePolicy"); positiveField.setAccessible(true); Field negativeField = cachePolicyClass.getDeclaredField("negativeCachePolicy"); negativeField.setAccessible(true); int[] result = new int[2]; synchronized (cachePolicyClass) { result[0] = positiveField.getInt(null); result[1] = negativeField.getInt(null); positiveField.setInt(null, positive); negativeField.setInt(null, negative); } return result; } catch (Exception e) { throw new RuntimeException( "Unexpected exception when setting DNS cache: " + e, e); } } /** * This implementation returns the loopback address for hosts that match a * current alias. */ @Override public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException { if (!aliases.contains(host)) { logger.info("LocalAliasNameService.lookupAllHostAddr:" + " Unknown host: " + host); throw new UnknownHostException("Unknown host: " + host); } final InetAddress lh = getLocalHost(); logger.info("LocalAliasNameService.lookupAllHostAddr:" + host + " => " + lh); return new InetAddress[] { lh }; } /** * Compute the local host if needed, avoiding circularities. The main name * service provider should provide the local host when called from * InetAddress.getLocalHost, so it should be OK to throw * UnknownHostException if this method is called recursively. */ private static synchronized InetAddress getLocalHost() throws UnknownHostException { if (localHost == null) { if (computingLocalHost) { throw new UnknownHostException("Local host"); } computingLocalHost = true; try { localHost = InetAddress.getLocalHost(); } finally { computingLocalHost = false; } } return localHost; } /** * This implementation returns one of the current aliases if the argument * matches the loopback address and there is at least one alias. */ @Override public String getHostByAddr(byte[] addr) throws UnknownHostException { final InetAddress inetAddr = InetAddress.getByAddress(addr); if (!getLocalHost().equals(inetAddr.getHostAddress())) { logger.info("LocalAliasNameService.getHostByAddr:" + " No mapping for address"); throw new UnknownHostException("No mapping for address"); } synchronized (aliases) { final Iterator iter = aliases.iterator(); if (iter.hasNext()) { String hostname = iter.next(); logger.info("LocalAliasNameService.getHostByAddr: " + hostname); return hostname; } } logger.info("LocalAliasNameService.getHostByAddr:" + " No mapping for address"); throw new UnknownHostException("No mapping for address"); } }