/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.util.network; import alluxio.AlluxioURI; import alluxio.Configuration; import alluxio.MasterInquireClient; import alluxio.PropertyKey; import alluxio.exception.PreconditionMessage; import alluxio.util.OSUtils; import alluxio.wire.WorkerNetAddress; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import io.netty.channel.unix.DomainSocketAddress; import org.apache.thrift.transport.TServerSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.reflect.Field; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import javax.annotation.concurrent.ThreadSafe; /** * Common network address related utilities shared by all components in Alluxio. */ @ThreadSafe public final class NetworkAddressUtils { private static final Logger LOG = LoggerFactory.getLogger(NetworkAddressUtils.class); public static final String WILDCARD_ADDRESS = "0.0.0.0"; /** * Checks if the underlying OS is Windows. */ public static final boolean WINDOWS = OSUtils.isWindows(); private static String sLocalHost; private static String sLocalIP; private NetworkAddressUtils() {} /** * Different types of services that client uses to connect. These types also indicate the service * bind address. */ public enum ServiceType { /** * Master RPC service (Thrift). */ MASTER_RPC("Alluxio Master RPC service", PropertyKey.MASTER_HOSTNAME, PropertyKey.MASTER_BIND_HOST, PropertyKey.MASTER_RPC_PORT), /** * Master web service (Jetty). */ MASTER_WEB("Alluxio Master Web service", PropertyKey.MASTER_WEB_HOSTNAME, PropertyKey.MASTER_WEB_BIND_HOST, PropertyKey.MASTER_WEB_PORT), /** * Worker RPC service (Thrift). */ WORKER_RPC("Alluxio Worker RPC service", PropertyKey.WORKER_HOSTNAME, PropertyKey.WORKER_BIND_HOST, PropertyKey.WORKER_RPC_PORT), /** * Worker data service (Netty). */ WORKER_DATA("Alluxio Worker data service", PropertyKey.WORKER_DATA_HOSTNAME, PropertyKey.WORKER_DATA_BIND_HOST, PropertyKey.WORKER_DATA_PORT), /** * Worker web service (Jetty). */ WORKER_WEB("Alluxio Worker Web service", PropertyKey.WORKER_WEB_HOSTNAME, PropertyKey.WORKER_WEB_BIND_HOST, PropertyKey.WORKER_WEB_PORT), /** * Proxy web service (Jetty). */ PROXY_WEB("Alluxio Proxy Web service", PropertyKey.PROXY_WEB_HOSTNAME, PropertyKey.PROXY_WEB_BIND_HOST, PropertyKey.PROXY_WEB_PORT), ; // service name private final String mServiceName; // the key of connect hostname private final PropertyKey mHostNameKey; // the key of bind hostname private final PropertyKey mBindHostKey; // the key of service port private final PropertyKey mPortKey; ServiceType(String serviceName, PropertyKey hostNameKey, PropertyKey bindHostKey, PropertyKey portKey) { mServiceName = serviceName; mHostNameKey = hostNameKey; mBindHostKey = bindHostKey; mPortKey = portKey; } /** * Gets service name. * * @return service name */ public String getServiceName() { return mServiceName; } /** * Gets the key of connect hostname. * * @return key of connect hostname */ public PropertyKey getHostNameKey() { return mHostNameKey; } /** * Gets the key of bind hostname. * * @return key of bind hostname */ public PropertyKey getBindHostKey() { return mBindHostKey; } /** * Gets the key of service port. * * @return key of service port */ public PropertyKey getPortKey() { return mPortKey; } /** * Gets the default port number on service. * * @return default port */ public int getDefaultPort() { return Integer.parseInt(mPortKey.getDefaultValue()); } } /** * Checks if the given port is valid. * * @param port the port to check */ public static void assertValidPort(final int port) { Preconditions.checkArgument(port < 65536, "Port must be less than 65536"); Preconditions.checkArgument(port >= 0, "Port must be non-negative"); } /** * Checks if the given port in the address is valid. * * @param address the {@link InetSocketAddress} with the port to check */ public static void assertValidPort(final InetSocketAddress address) { assertValidPort(address.getPort()); } /** * Helper method to get the {@link InetSocketAddress} address for client to communicate with the * service. * * @param service the service name used to connect * @return the service address that a client (typically outside the service machine) uses to * communicate with service. */ public static InetSocketAddress getConnectAddress(ServiceType service) { return new InetSocketAddress(getConnectHost(service), getPort(service)); } /** * Provides an externally resolvable hostname for client to communicate with the service. If the * hostname is not explicitly specified, Alluxio will try to use the bind host. If the bind host * is wildcard, Alluxio will automatically determine an appropriate hostname from local machine. * The various possibilities shown in the following table: * <table> * <caption>Hostname Scenarios</caption> * <thead> * <tr> * <th>Specified Hostname</th> * <th>Specified Bind Host</th> * <th>Returned Connect Host</th> * </tr> * </thead> * <tbody> * <tr> * <td>hostname</td> * <td>hostname</td> * <td>hostname</td> * </tr> * <tr> * <td>not defined</td> * <td>hostname</td> * <td>hostname</td> * </tr> * <tr> * <td>hostname</td> * <td>0.0.0.0 or not defined</td> * <td>hostname</td> * </tr> * <tr> * <td>not defined</td> * <td>0.0.0.0 or not defined</td> * <td>localhost</td> * </tr> * </tbody> * </table> * * @param service Service type used to connect * @return the externally resolvable hostname that the client can use to communicate with the * service. */ public static String getConnectHost(ServiceType service) { if (Configuration.containsKey(service.mHostNameKey)) { String connectHost = Configuration.get(service.mHostNameKey); if (!connectHost.isEmpty() && !connectHost.equals(WILDCARD_ADDRESS)) { return connectHost; } } if (Configuration.containsKey(service.mBindHostKey)) { String bindHost = Configuration.get(service.mBindHostKey); if (!bindHost.isEmpty() && !bindHost.equals(WILDCARD_ADDRESS)) { return bindHost; } } return getLocalHostName(); } /** * Gets the port number on a given service type. If user defined port number is not explicitly * specified, Alluxio will use the default service port. * * @param service Service type used to connect * @return the service port number */ public static int getPort(ServiceType service) { return Configuration.getInt(service.mPortKey); } /** * Helper method to get the {@link InetSocketAddress} bind address on a given service. * <p> * Host bind information searching order: * <ol> * <li>System properties or environment variables via alluxio-env.sh * <li>Default properties via alluxio-default.properties file * <li>A externally resolvable local hostname for the host this JVM is running on * </ol> * * @param service the service name used to connect * @return the InetSocketAddress the service will bind to */ public static InetSocketAddress getBindAddress(ServiceType service) { int port = getPort(service); assertValidPort(port); String host; if (Configuration.containsKey(service.mBindHostKey) && !Configuration.get(service.mBindHostKey) .isEmpty()) { host = Configuration.get(service.mBindHostKey); } else { host = getLocalHostName(); } return new InetSocketAddress(host, port); } /** * Gets the local hostname to be used by the client. If this isn't configured, a non-loopback * local hostname will be looked up. * * @return the local hostname for the client */ public static String getClientHostName() { if (Configuration.containsKey(PropertyKey.USER_HOSTNAME)) { return Configuration.get(PropertyKey.USER_HOSTNAME); } return getLocalHostName(); } /** * Gets a local hostname for the host this JVM is running on. * * @return the local host name, which is not based on a loopback ip address */ public static synchronized String getLocalHostName() { if (sLocalHost != null) { return sLocalHost; } int hostResolutionTimeout = Configuration.getInt(PropertyKey.NETWORK_HOST_RESOLUTION_TIMEOUT_MS); return getLocalHostName(hostResolutionTimeout); } /** * Gets a local host name for the host this JVM is running on. * * @param timeoutMs Timeout in milliseconds to use for checking that a possible local host is * reachable * @return the local host name, which is not based on a loopback ip address */ public static synchronized String getLocalHostName(int timeoutMs) { if (sLocalHost != null) { return sLocalHost; } try { sLocalHost = InetAddress.getByName(getLocalIpAddress(timeoutMs)).getCanonicalHostName(); return sLocalHost; } catch (UnknownHostException e) { throw Throwables.propagate(e); } } /** * Gets a local IP address for the host this JVM is running on. * * @return the local ip address, which is not a loopback address and is reachable */ public static synchronized String getLocalIpAddress() { if (sLocalIP != null) { return sLocalIP; } int hostResolutionTimeout = Configuration.getInt(PropertyKey.NETWORK_HOST_RESOLUTION_TIMEOUT_MS); return getLocalIpAddress(hostResolutionTimeout); } /** * Gets a local IP address for the host this JVM is running on. * * @param timeoutMs Timeout in milliseconds to use for checking that a possible local IP is * reachable * @return the local ip address, which is not a loopback address and is reachable */ public static synchronized String getLocalIpAddress(int timeoutMs) { if (sLocalIP != null) { return sLocalIP; } try { InetAddress address = InetAddress.getLocalHost(); LOG.debug("address: {} isLoopbackAddress: {}, with host {} {}", address, address.isLoopbackAddress(), address.getHostAddress(), address.getHostName()); // Make sure that the address is actually reachable since in some network configurations // it is possible for the InetAddress.getLocalHost() call to return a non-reachable // address e.g. a broadcast address if (!isValidAddress(address, timeoutMs)) { Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); // Make getNetworkInterfaces have the same order of network interfaces as listed on // unix-like systems. This optimization can help avoid to get some special addresses, such // as loopback address"127.0.0.1", virtual bridge address "192.168.122.1" as far as // possible. if (!WINDOWS) { List<NetworkInterface> netIFs = Collections.list(networkInterfaces); Collections.reverse(netIFs); networkInterfaces = Collections.enumeration(netIFs); } while (networkInterfaces.hasMoreElements()) { NetworkInterface ni = networkInterfaces.nextElement(); Enumeration<InetAddress> addresses = ni.getInetAddresses(); while (addresses.hasMoreElements()) { address = addresses.nextElement(); // Address must not be link local or loopback. And it must be reachable if (isValidAddress(address, timeoutMs)) { sLocalIP = address.getHostAddress(); return sLocalIP; } } } LOG.warn("Your hostname, {} resolves to a loopback/non-reachable address: {}, " + "but we couldn't find any external IP address!", InetAddress.getLocalHost().getHostName(), address.getHostAddress()); } sLocalIP = address.getHostAddress(); return sLocalIP; } catch (IOException e) { throw Throwables.propagate(e); } } /** * @param host the host to try to connect to * @param port the port to try to connect on * @return whether a socket connection can be made to the given host on the given port */ public static boolean isServing(String host, int port) { if (port < 0) { return false; } try { Socket socket = new Socket(host, port); socket.close(); return true; } catch (IOException e) { return false; } } /** * Tests if the address is externally resolvable. Address must not be wildcard, link local, * loopback address, non-IPv4, or other unreachable addresses. * * @param address The testing address * @param timeoutMs Timeout in milliseconds to use for checking that a possible local IP is * reachable * @return a {@code boolean} indicating if the given address is externally resolvable address */ private static boolean isValidAddress(InetAddress address, int timeoutMs) throws IOException { return !address.isAnyLocalAddress() && !address.isLinkLocalAddress() && !address.isLoopbackAddress() && address.isReachable(timeoutMs) && (address instanceof Inet4Address); } /** * Replaces and resolves the hostname in a given address or path string. * * @param path an address or path string, e.g., "hdfs://host:port/dir", "file:///dir", "/dir" * @return an address or path string with hostname resolved, or the original path intact if no * hostname is embedded, or null if the given path is null or empty. * @throws UnknownHostException if the hostname cannot be resolved */ public static AlluxioURI replaceHostName(AlluxioURI path) throws UnknownHostException { if (path == null) { return null; } if (path.hasAuthority()) { String authority = resolveHostName(path.getHost()); if (path.getPort() != -1) { authority += ":" + path.getPort(); } return new AlluxioURI(path.getScheme(), authority, path.getPath(), path.getQueryMap()); } return path; } /** * Resolves a given hostname by a canonical hostname. When a hostname alias (e.g., those specified * in /etc/hosts) is given, the alias may not be resolvable on other hosts in a cluster unless the * same alias is defined there. In this situation, loadufs would break. * * @param hostname the input hostname, which could be an alias * @return the canonical form of the hostname, or null if it is null or empty * @throws UnknownHostException if the given hostname cannot be resolved */ public static String resolveHostName(String hostname) throws UnknownHostException { if (hostname == null || hostname.isEmpty()) { return null; } return InetAddress.getByName(hostname).getCanonicalHostName(); } /** * Gets FQDN(Full Qualified Domain Name) from Java representations of network address, except * String representation which should be handled by {@link #resolveHostName(String)} which will * handle the situation where hostname is null. * * @param addr the input network address representation, can not be null * @return the resolved FQDN host name */ public static String getFqdnHost(InetSocketAddress addr) { Preconditions.checkNotNull(addr.getAddress(), "the address of " + addr + " is invalid."); return addr.getAddress().getCanonicalHostName(); } /** * Gets FQDN(Full Qualified Domain Name) from Alluxio representation of network address. * * @param addr the input network address representation * @return the resolved FQDN host name * @throws UnknownHostException if the host is not known */ public static String getFqdnHost(WorkerNetAddress addr) throws UnknownHostException { return resolveHostName(addr.getHost()); } /** * Gets the port for the underline socket. This function calls * {@link #getThriftSocket(org.apache.thrift.transport.TServerSocket)}, so reflection will be used * to get the port. * * @param thriftSocket the underline socket * @return the thrift port for the underline socket * @see #getThriftSocket(org.apache.thrift.transport.TServerSocket) */ public static int getThriftPort(TServerSocket thriftSocket) { return getThriftSocket(thriftSocket).getLocalPort(); } /** * Extracts the port from the thrift socket. As of thrift 0.9, the internal socket used is not * exposed in the API, so this function will use reflection to get access to it. * * @param thriftSocket the underline thrift socket * @return the server socket */ public static ServerSocket getThriftSocket(final TServerSocket thriftSocket) { try { Field field = TServerSocket.class.getDeclaredField("serverSocket_"); field.setAccessible(true); return (ServerSocket) field.get(thriftSocket); } catch (NoSuchFieldException | IllegalAccessException e) { throw Throwables.propagate(e); } } /** * Parses {@link InetSocketAddress} from a String. * * @param address socket address to parse * @return InetSocketAddress of the String */ public static InetSocketAddress parseInetSocketAddress(String address) throws IOException { if (address == null) { return null; } String[] strArr = address.split(":"); if (strArr.length != 2) { throw new IOException("Invalid InetSocketAddress " + address); } return new InetSocketAddress(strArr[0], Integer.parseInt(strArr[1])); } /** * Extracts rpcPort InetSocketAddress from Alluxio representation of network address. * * @param netAddress the input network address representation * @return InetSocketAddress */ public static InetSocketAddress getRpcPortSocketAddress(WorkerNetAddress netAddress) { String host = netAddress.getHost(); int port = netAddress.getRpcPort(); return new InetSocketAddress(host, port); } /** * Extracts dataPort socket address from Alluxio representation of network address. * * @param netAddress the input network address representation * @return the socket address */ public static SocketAddress getDataPortSocketAddress(WorkerNetAddress netAddress) { SocketAddress address; if (NettyUtils.isDomainSocketSupported(netAddress)) { address = new DomainSocketAddress(netAddress.getDomainSocketPath()); } else { String host = netAddress.getHost(); int port = netAddress.getDataPort(); address = new InetSocketAddress(host, port); } return address; } /** * Get the active master address from zookeeper for the fault tolerant Alluxio masters. * * @param zkLeaderPath the Zookeeper path containing the leader master address * @return InetSocketAddress the active master address retrieved from zookeeper */ public static InetSocketAddress getLeaderAddressFromZK(String zkLeaderPath) { Preconditions.checkState(Configuration.containsKey(PropertyKey.ZOOKEEPER_ADDRESS), PreconditionMessage.ERR_ZK_ADDRESS_NOT_SET.toString(), PropertyKey.ZOOKEEPER_ADDRESS.toString()); Preconditions.checkState(Configuration.containsKey(PropertyKey.ZOOKEEPER_ELECTION_PATH), PropertyKey.ZOOKEEPER_ELECTION_PATH.toString()); MasterInquireClient masterInquireClient = MasterInquireClient.getClient( Configuration.get(PropertyKey.ZOOKEEPER_ADDRESS), Configuration.get(PropertyKey.ZOOKEEPER_ELECTION_PATH), zkLeaderPath); try { String temp = masterInquireClient.getLeaderAddress(); return NetworkAddressUtils.parseInetSocketAddress(temp); } catch (IOException e) { LOG.error(e.getMessage(), e); throw Throwables.propagate(e); } } /** * @return InetSocketAddress the list of all master addresses from zookeeper */ public static List<InetSocketAddress> getMasterAddressesFromZK() { Preconditions.checkState(Configuration.containsKey(PropertyKey.ZOOKEEPER_ADDRESS)); Preconditions.checkState(Configuration.containsKey(PropertyKey.ZOOKEEPER_ELECTION_PATH)); Preconditions.checkState(Configuration.containsKey(PropertyKey.ZOOKEEPER_LEADER_PATH)); MasterInquireClient masterInquireClient = MasterInquireClient.getClient( Configuration.get(PropertyKey.ZOOKEEPER_ADDRESS), Configuration.get(PropertyKey.ZOOKEEPER_ELECTION_PATH), Configuration.get(PropertyKey.ZOOKEEPER_LEADER_PATH)); List<String> addresses = masterInquireClient.getMasterAddresses(); if (addresses == null) { throw new RuntimeException(String.format("Failed to get the master addresses from zookeeper, " + "zookeeper address: %s", Configuration.get(PropertyKey.ZOOKEEPER_ADDRESS))); } List<InetSocketAddress> ret = new ArrayList<>(addresses.size()); try { for (String address : addresses) { ret.add(NetworkAddressUtils.parseInetSocketAddress(address)); } return ret; } catch (IOException e) { throw Throwables.propagate(e); } } }