/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltcore.network; import java.net.InetAddress; import java.util.concurrent.ExecutionException; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.voltcore.utils.CoreUtils; import com.google_voltpatches.common.base.Function; import com.google_voltpatches.common.cache.Cache; import com.google_voltpatches.common.cache.CacheBuilder; /** * A configurable cache mapping from InetAddress to the hostnames of InetAddresses. * Tracks failed lookups allowing for a longer timeout to be specified. This saves on allocating threads * to DNS lookups that will time out and works around the lack of async DNS lookups in Java */ public class ReverseDNSCache { private static volatile ThreadPoolExecutor m_es = new ThreadPoolExecutor(1, 16, 1, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), CoreUtils.getThreadFactory("Reverse DNS lookups")); public static final long DEFAULT_MAX_SUCCESS = 1000 * 10; public static final long DEFAULT_MAX_FAILURE = 1000 * 10; public static final long DEFAULT_SUCCESS_TIMEOUT = 600L; //10 minutes public static final long DEFAULT_FAILURE_TIMEOUT = 3600L; //1 hr public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS; private static final Function<InetAddress, String> DNS_RESOLVER = new Function<InetAddress, String>() { @Override public String apply(java.net.InetAddress inetAddress) { return inetAddress.getHostName(); } }; private static final ReverseDNSCache m_instance = new ReverseDNSCache( DEFAULT_MAX_SUCCESS, DEFAULT_MAX_FAILURE, DEFAULT_SUCCESS_TIMEOUT, DEFAULT_FAILURE_TIMEOUT, DEFAULT_TIMEOUT_UNIT); private static final String DUMMY = ""; private final Cache<InetAddress, String> m_successes; private final Cache<InetAddress, String> m_failures; public static synchronized void start() { if (m_es == null) { m_es = new ThreadPoolExecutor(1, 16, 1, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), CoreUtils.getThreadFactory("Reverse DNS lookups")); try { m_es.submit(new Runnable() { @Override public void run() {} }).get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException("Unable to prime ReverseDNSCache", e); } } } public static synchronized void stop() throws InterruptedException{ if (m_es != null) { m_es.shutdown(); try { m_es.awaitTermination(365, TimeUnit.DAYS); } catch (InterruptedException e) { throw new RuntimeException("Unable to shutdown ReverseDNSCache", e); } m_es = null; } } public static void submit(Runnable r) { final ThreadPoolExecutor es = m_es; if (es == null || es.isShutdown()) { throw new IllegalStateException("ReverseDNSCache is closed"); } es.submit(r); } /** * Passing in null for entries or timeout results in no limit being set * * Timeout is based on the last time a lookup was performed and not access time * @param successEntries * @param failureEntries * @param successTimeout * @param failureTimeout * @param timeoutUnit */ public ReverseDNSCache( Long successEntries, Long failureEntries, Long successTimeout, Long failureTimeout, TimeUnit timeoutUnit) { m_successes = getCache(successEntries, successTimeout, timeoutUnit); m_failures = getCache(failureEntries, failureTimeout, timeoutUnit); } public Cache<InetAddress, String> getCache(Long entries, Long timeout, TimeUnit timeoutUnit) { CacheBuilder<Object, Object> b = CacheBuilder.newBuilder(); if (entries != null) b.maximumSize(entries); if (timeout != null) b.expireAfterWrite(timeout, timeoutUnit); return b.build(); } public String getHostnameOrAddress(InetAddress address) { //Check for it in the success cache String hostname = m_successes.getIfPresent(address); if (hostname == null) { //Check for it in the failure cache hostname = m_failures.getIfPresent(address); if (hostname != null) { //Lookup failed recently, return the address string return address.getHostAddress(); } } else { return hostname; } //It's not in either cache, do the lookup and see if it succeeded. hostname = DNS_RESOLVER.apply(address); if (hostname.equals(address.getHostAddress())) { m_failures.put(address, DUMMY); } else { m_successes.put(address, hostname); } return hostname; } public static String hostnameOrAddress(InetAddress address) { return m_instance.getHostnameOrAddress(address); } }