/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 org.apache.brooklyn.core.location.geo; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.StringPredicates; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Durations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicates; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; public class LocalhostExternalIpLoader { public static final Logger LOG = LoggerFactory.getLogger(LocalhostExternalIpLoader.class); /** * Mutex to guard access to retrievingLocalExternalIp. */ private static final Object mutex = new Object(); /** * When null there is no ongoing attempt to load the external IP address. Either no attempt has been made or the * last attempt has been completed. * When set there is an ongoing attempt to load the external IP address. New attempts to lookup the external IP * address should wait on this latch instead of making another attempt to load the IP address. */ private static CountDownLatch retrievingLocalExternalIp; /** * Cached external IP address of localhost. Null if either no attempt has been made to resolve the address or the * last attempt failed. */ private static volatile String localExternalIp; private static class IpLoader implements Callable<String> { private static final Pattern ipPattern = Pattern.compile( "\\b((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\b"); final String url; protected IpLoader(String url) { this.url = url; } @Override public String call() { String response = ResourceUtils.create(LocalhostExternalIpLoader.class) .getResourceAsString(url).trim(); return postProcessResponse(response); } String postProcessResponse(String response) { Matcher matcher = ipPattern.matcher(response); boolean matched = matcher.find(); if (!matched) { LOG.error("No IP address matched in output from {}: {}", url, response); return null; } else { return matcher.group(); } } } @VisibleForTesting static List<String> getIpAddressWebsites() { String file = new ResourceUtils(LocalhostExternalIpLoader.class) .getResourceAsString("classpath://org/apache/brooklyn/location/geo/external-ip-address-resolvers.txt"); Iterable<String> lines = Splitter.on('\n') .omitEmptyStrings() .trimResults() .split(file); List<String> urls = Lists.newArrayList(Iterables.filter(lines, Predicates.not(StringPredicates.startsWith("#")))); Collections.shuffle(urls); return urls; } @VisibleForTesting static String getIpAddressFrom(String url) { return new IpLoader(url).call(); } /** As {@link #getLocalhostIpWithin(Duration)} but returning 127.0.0.1 if not accessible */ public static String getLocalhostIpQuicklyOrDefault() { String result = doLoad(Duration.seconds(2)); if (result==null) return "127.0.0.1"; return result; } /** As {@link #getLocalhostIpWithin(Duration)} but without the time limit cut-off, failing if the load gives an error. */ public static String getLocalhostIpWaiting() { return getLocalhostIpWithin(null); } /** * Attempts to load the public IP address of localhost, failing if the load * does not complete within the given duration. * @return The public IP address of localhost */ public static String getLocalhostIpWithin(Duration timeout) { String result = doLoad(timeout); if (result == null) { throw new IllegalStateException("Unable to retrieve external IP for localhost; network may be down or slow or remote service otherwise not responding"); } return result; } /** * Requests URLs returned by {@link #getIpAddressWebsites()} until one returns an IP address or all URLs have been tried. * The address is assumed to be the external IP address of localhost. * @param blockFor The maximum duration to wait for the IP address to be resolved. * An indefinite way if null. * @return A string in IPv4 format, or null if no such address could be ascertained. */ private static String doLoad(Duration blockFor) { // Check for a cached external IP address final String resolvedIp = localExternalIp; if (resolvedIp != null) { return resolvedIp; } // Check for an ongoing attempt to load an external IP address final boolean startAttemptToLoadIp; final CountDownLatch attemptToRetrieveLocalExternalIp; synchronized (mutex) { if (retrievingLocalExternalIp == null) { retrievingLocalExternalIp = new CountDownLatch(1); startAttemptToLoadIp = true; } else { startAttemptToLoadIp = false; } attemptToRetrieveLocalExternalIp = retrievingLocalExternalIp; } // Attempt to load the external IP address in private thread, otherwise blocks for 30s+ on dodgy network! // (we can skip it if someone else is doing it, we have synch lock so we'll get notified) if (startAttemptToLoadIp) { final List<String> candidateUrls = getIpAddressWebsites(); if (candidateUrls.isEmpty()) { LOG.debug("No candidate URLs to use to determine external IP of localhost"); return null; } new Thread() { public void run() { for (String url : candidateUrls) { try { LOG.debug("Looking up external IP of this host from {} in private thread {}", url, Thread.currentThread()); final String loadedIp = new IpLoader(url).call(); localExternalIp = loadedIp; LOG.debug("Finished looking up external IP of this host from {} in private thread, result {}", url, loadedIp); break; } catch (Throwable t) { LOG.debug("Unable to look up external IP of this host from {}, probably offline {})", url, t); } } attemptToRetrieveLocalExternalIp.countDown(); synchronized (mutex) { retrievingLocalExternalIp = null; } } }.start(); } try { if (blockFor!=null) { Durations.await(attemptToRetrieveLocalExternalIp, blockFor); } else { attemptToRetrieveLocalExternalIp.await(); } } catch (InterruptedException e) { throw Exceptions.propagate(e); } return localExternalIp; } }