| /* |
| * 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.geode.internal.inet; |
| |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| import static org.apache.geode.internal.Retry.tryFor; |
| |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.NetworkInterface; |
| import java.net.SocketException; |
| import java.net.UnknownHostException; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.TimeoutException; |
| |
| import javax.naming.Context; |
| import javax.naming.NamingEnumeration; |
| import javax.naming.directory.Attribute; |
| import javax.naming.directory.Attributes; |
| import javax.naming.directory.DirContext; |
| import javax.naming.directory.InitialDirContext; |
| |
| import org.apache.geode.annotations.internal.MakeNotStatic; |
| |
| /** |
| * LocalHostUtil provides lookup for the preferred local host InetAddress. It makes up |
| * for deficiencies in /etc/hosts configuration and tries to ensure that, if available, |
| * a non-loopback address is used. |
| */ |
| |
| public class LocalHostUtil { |
| /** |
| * Optional system property to enable GemFire usage of link-local addresses |
| */ |
| private static final String USE_LINK_LOCAL_ADDRESSES_PROPERTY = |
| "gemfire.net.useLinkLocalAddresses"; |
| |
| /** |
| * True if GemFire should use link-local addresses |
| */ |
| private static final boolean useLinkLocalAddresses = |
| Boolean.getBoolean(USE_LINK_LOCAL_ADDRESSES_PROPERTY); |
| |
| /** |
| * all classes should use this variable to determine whether to use IPv4 or IPv6 addresses |
| */ |
| @MakeNotStatic |
| private static boolean useIPv6Addresses = !Boolean.getBoolean("java.net.preferIPv4Stack") |
| && Boolean.getBoolean("java.net.preferIPv6Addresses"); |
| |
| /** |
| * Resolves local host. Will retry if resolution fails. |
| * |
| * @return local host if resolved otherwise null. |
| */ |
| private static InetAddress tryToResolveLocalHost() { |
| try { |
| return tryFor(60, SECONDS, 1, SECONDS, LocalHostUtil::resolveLocalHost, Objects::nonNull); |
| } catch (TimeoutException | InterruptedException ignored) { |
| } |
| return null; |
| } |
| |
| private static InetAddress resolveLocalHost() { |
| InetAddress inetAddress = null; |
| try { |
| inetAddress = InetAddress.getByAddress(InetAddress.getLocalHost().getAddress()); |
| if (inetAddress.isLoopbackAddress()) { |
| InetAddress ipv4Fallback = null; |
| InetAddress ipv6Fallback = null; |
| // try to find a non-loopback address |
| Set<InetAddress> myInterfaces = getMyAddresses(); |
| boolean preferIPv6 = useIPv6Addresses; |
| String lhName = null; |
| for (InetAddress addr : myInterfaces) { |
| if (addr.isLoopbackAddress() || addr.isAnyLocalAddress() || lhName != null) { |
| break; |
| } |
| boolean ipv6 = addr instanceof Inet6Address; |
| boolean ipv4 = addr instanceof Inet4Address; |
| if ((preferIPv6 && ipv6) || (!preferIPv6 && ipv4)) { |
| String addrName = reverseDNS(addr); |
| if (inetAddress.isLoopbackAddress()) { |
| inetAddress = addr; |
| lhName = addrName; |
| } else if (addrName != null) { |
| inetAddress = addr; |
| lhName = addrName; |
| } |
| } else { |
| if (preferIPv6 && ipv4 && ipv4Fallback == null) { |
| ipv4Fallback = addr; |
| } else if (!preferIPv6 && ipv6 && ipv6Fallback == null) { |
| ipv6Fallback = addr; |
| } |
| } |
| } |
| // vanilla Ubuntu installations will have a usable IPv6 address when |
| // running as a guest OS on an IPv6-enabled machine. We also look for |
| // the alternative IPv4 configuration. |
| if (inetAddress.isLoopbackAddress()) { |
| if (ipv4Fallback != null) { |
| inetAddress = ipv4Fallback; |
| useIPv6Addresses = false; |
| } else if (ipv6Fallback != null) { |
| inetAddress = ipv6Fallback; |
| useIPv6Addresses = true; |
| } |
| } |
| } |
| } catch (UnknownHostException ignored) { |
| } |
| return inetAddress; |
| } |
| |
| /** |
| * Cache local host to avoid lookup costs. |
| */ |
| private static final InetAddress localHost = tryToResolveLocalHost(); |
| |
| /** |
| * returns a set of the non-loopback InetAddresses for this machine |
| */ |
| public static Set<InetAddress> getMyAddresses() { |
| Set<InetAddress> result = new HashSet<>(); |
| Set<InetAddress> locals = new HashSet<>(); |
| Enumeration<NetworkInterface> interfaces; |
| try { |
| interfaces = NetworkInterface.getNetworkInterfaces(); |
| } catch (SocketException e) { |
| throw new IllegalArgumentException( |
| "Unable to examine network interfaces", |
| e); |
| } |
| while (interfaces.hasMoreElements()) { |
| NetworkInterface face = interfaces.nextElement(); |
| boolean faceIsUp = false; |
| try { |
| faceIsUp = face.isUp(); |
| } catch (SocketException e) { |
| // since it's not usable we'll skip this interface |
| } |
| if (faceIsUp) { |
| Enumeration<InetAddress> addrs = face.getInetAddresses(); |
| while (addrs.hasMoreElements()) { |
| InetAddress addr = addrs.nextElement(); |
| if (addr.isLoopbackAddress() || addr.isAnyLocalAddress() |
| || (!useLinkLocalAddresses && addr.isLinkLocalAddress())) { |
| locals.add(addr); |
| } else { |
| result.add(addr); |
| } |
| } // while |
| } |
| } // while |
| // fix for bug #42427 - allow product to run on a standalone box by using |
| // local addresses if there are no non-local addresses available |
| if (result.size() == 0) { |
| return locals; |
| } else { |
| return result; |
| } |
| } |
| |
| /** |
| * This method uses JNDI to look up an address in DNS and return its name |
| * |
| * |
| * @return the host name associated with the address or null if lookup isn't possible or there is |
| * no host name for this address |
| */ |
| private static String reverseDNS(InetAddress addr) { |
| byte[] addrBytes = addr.getAddress(); |
| // reverse the address suitable for reverse lookup |
| StringBuilder lookup = new StringBuilder(); |
| for (int index = addrBytes.length - 1; index >= 0; index--) { |
| lookup.append(addrBytes[index] & 0xff).append('.'); |
| } |
| lookup.append("in-addr.arpa"); |
| |
| try { |
| Hashtable<String, String> env = new Hashtable<>(); |
| env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); |
| DirContext ctx = new InitialDirContext(env); |
| Attributes attrs = ctx.getAttributes(lookup.toString(), new String[] {"PTR"}); |
| for (NamingEnumeration ae = attrs.getAll(); ae.hasMoreElements();) { |
| Attribute attr = (Attribute) ae.next(); |
| for (Enumeration vals = attr.getAll(); vals.hasMoreElements();) { |
| Object elem = vals.nextElement(); |
| if ("PTR".equals(attr.getID()) && elem != null) { |
| return elem.toString(); |
| } |
| } |
| } |
| ctx.close(); |
| } catch (Exception e) { |
| // ignored |
| } |
| return null; |
| } |
| |
| /** |
| * All classes should use this instead of relying on the JRE system property |
| */ |
| public static boolean preferIPv6Addresses() { |
| return useIPv6Addresses; |
| } |
| |
| /** |
| * All GemFire code should use this method instead of InetAddress.getLocalHost(). See bug #40619 |
| */ |
| public static InetAddress getLocalHost() throws UnknownHostException { |
| if (localHost == null) { |
| throw new UnknownHostException(); |
| } |
| return localHost; |
| } |
| |
| /** |
| * Returns the special address that can be used to bind to all local addresses. |
| * In most cases this will be "0.0.0.0". |
| */ |
| public static InetAddress getAnyLocalAddress() { |
| return new InetSocketAddress(0).getAddress(); |
| } |
| |
| /** |
| * Returns true if host matches the LOCALHOST. |
| */ |
| public static boolean isLocalHost(Object host) { |
| if (host instanceof InetAddress) { |
| InetAddress inetAddress = (InetAddress) host; |
| if (isLocalHost(inetAddress)) { |
| return true; |
| } else if (inetAddress.isLoopbackAddress()) { |
| return true; |
| } else if (inetAddress.isAnyLocalAddress()) { |
| return true; |
| } else { |
| try { |
| Enumeration en = NetworkInterface.getNetworkInterfaces(); |
| while (en.hasMoreElements()) { |
| NetworkInterface i = (NetworkInterface) en.nextElement(); |
| for (Enumeration en2 = i.getInetAddresses(); en2.hasMoreElements();) { |
| InetAddress addr = (InetAddress) en2.nextElement(); |
| if (inetAddress.equals(addr)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } catch (SocketException e) { |
| throw new IllegalArgumentException("Unable to query network interface", e); |
| } |
| } |
| } else { |
| return isLocalHost((Object) toInetAddress(host.toString())); |
| } |
| } |
| |
| private static boolean isLocalHost(InetAddress host) { |
| try { |
| return getLocalHost().equals(host); |
| } catch (UnknownHostException ignored) { |
| return false; |
| } |
| } |
| |
| /** |
| * Converts the string host to an instance of InetAddress. Returns null if the string is empty. |
| * Fails Assertion if the conversion would result in <code>java.lang.UnknownHostException</code>. |
| * <p> |
| * Any leading slashes on host will be ignored. |
| * |
| * @param host string version the InetAddress |
| * |
| * @return the host converted to InetAddress instance |
| * |
| * @throws IllegalArgumentException in lieu of UnknownHostException |
| */ |
| public static InetAddress toInetAddress(String host) { |
| if (host == null || host.length() == 0) { |
| return null; |
| } |
| try { |
| final int index = host.indexOf("/"); |
| if (index > -1) { |
| return InetAddress.getByName(host.substring(index + 1)); |
| } else { |
| return InetAddress.getByName(host); |
| } |
| } catch (UnknownHostException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| } |
| |
| public static String getLocalHostName() throws UnknownHostException { |
| return getLocalHost().getHostName(); |
| } |
| |
| public static String getLocalHostString() throws UnknownHostException { |
| return getLocalHost().toString(); |
| } |
| |
| public static String getCanonicalLocalHostName() throws UnknownHostException { |
| return getLocalHost().getCanonicalHostName(); |
| |
| } |
| } |