blob: 90d6301de7a7a79ed31f47b430d436e3631a0147 [file] [log] [blame]
/*
* 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.servicecomb.foundation.common.net;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
public final class NetUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(NetUtils.class);
private static final String IPV4_KEY = "_v4";
private static final String IPV6_KEY = "_v6";
private static final String PREFERRED_INTERFACE = "eth";
// one interface can bind to multiple address
// we only save one ip for each interface name.
// eg:
// 1. eth0 -> ip1 ip2
// last data is eth0 -> ip2
// 2. eth0 -> ip1
// eth0:0 -> ip2
// eth0:1 -> ip3
// on interface name conflict, all data saved
// key is network interface name and type
private static Map<String, InetAddress> allInterfaceAddresses = new HashMap<>();
private static String hostName;
private static String hostAddress;
private static String hostAddressIpv6;
static {
doGetHostNameAndHostAddress();
}
private static void doGetHostNameAndHostAddress() {
try {
doGetAddressFromNetworkInterface();
// getLocalHost will throw exception in some docker image and sometimes will do a hostname lookup and time consuming
InetAddress localHost = InetAddress.getLocalHost();
hostName = localHost.getHostName();
LOGGER.info("localhost hostName={}, hostAddress={}.", hostName, localHost.getHostAddress());
if (!isLocalAddress(localHost)) {
if (Inet6Address.class.isInstance(localHost)) {
hostAddressIpv6 = trimIpv6(localHost.getHostAddress());
hostAddress = tryGetHostAddressFromNetworkInterface(false, localHost);
LOGGER.info("Host address info ipV4={}, ipV6={}.", hostAddress, hostAddressIpv6);
return;
}
hostAddress = localHost.getHostAddress();
hostAddressIpv6 = trimIpv6(tryGetHostAddressFromNetworkInterface(true, localHost));
LOGGER.info("Host address info ipV4={}, ipV6={}.", hostAddress, hostAddressIpv6);
return;
}
hostAddressIpv6 = trimIpv6(tryGetHostAddressFromNetworkInterface(true, localHost));
hostAddress = tryGetHostAddressFromNetworkInterface(false, localHost);
LOGGER.info("Host address info ipV4={}, ipV6={}.", hostAddress, hostAddressIpv6);
} catch (Exception e) {
LOGGER.error("got exception when trying to get addresses:", e);
if (allInterfaceAddresses.size() >= 1) {
InetAddress entry = allInterfaceAddresses.entrySet().iterator().next().getValue();
// get host name will do a reverse name lookup and is time consuming
hostName = entry.getHostName();
hostAddress = entry.getHostAddress();
LOGGER.info("add host name from interfaces:" + hostName + ",host address:" + hostAddress);
}
}
}
private static String tryGetHostAddressFromNetworkInterface(boolean isIpv6, InetAddress localhost) {
InetAddress result = null;
for (Entry<String, InetAddress> entry : allInterfaceAddresses.entrySet()) {
if (isIpv6 && entry.getKey().endsWith(IPV6_KEY)) {
result = entry.getValue();
if (entry.getKey().startsWith(PREFERRED_INTERFACE)) {
return result.getHostAddress();
}
} else if (!isIpv6 && entry.getKey().endsWith(IPV4_KEY)) {
result = entry.getValue();
if (entry.getKey().startsWith(PREFERRED_INTERFACE)) {
return result.getHostAddress();
}
}
}
if (result == null) {
return localhost.getHostAddress();
}
return result.getHostAddress();
}
private NetUtils() {
}
/**
* docker环境中,有时无法通过InetAddress.getLocalHost()获取 ,会报unknown host Exception, system error
* 此时,通过遍历网卡接口的方式规避,出来的数据不一定对
*/
private static void doGetAddressFromNetworkInterface() throws SocketException {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface network = networkInterfaces.nextElement();
if (!network.isUp() || network.isLoopback() || network.isVirtual()) {
continue;
}
Enumeration<InetAddress> addresses = network.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if (isLocalAddress(address)) {
continue;
}
if (address instanceof Inet4Address) {
LOGGER.info("add ipv4 network interface:" + network.getName() + ",host address:" + address.getHostAddress());
allInterfaceAddresses.put(network.getName() + IPV4_KEY, address);
} else if (address instanceof Inet6Address) {
LOGGER.info("add ipv6 network interface:" + network.getName() + ",host address:" + address.getHostAddress());
allInterfaceAddresses.put(network.getName() + IPV6_KEY, address);
}
}
}
}
private static String trimIpv6(String hostAddress) {
int index = hostAddress.indexOf("%");
if (index >= 0) {
return hostAddress.substring(0, index);
}
return hostAddress;
}
private static boolean isLocalAddress(InetAddress address) {
return address.isAnyLocalAddress() || address.isLoopbackAddress() || address.isMulticastAddress();
}
/**
* The format of address should be {@code IPv4:port} or {@code [IPv6]:port}, or {@code host:port},
* or you will not get expected result.
*
* Note that the IPv6 address should be wrapped by square brackets.
* @return IpPort parsed from input param, or {@code null} if the param is null.
*/
public static IpPort parseIpPort(String address) {
if (address == null) {
return null;
}
URI uri = URI.create("http://" + address);
return parseIpPort(uri, true);
}
/**
* Parse a {@link URI} into an {@link IpPort}.
*
* <p>
* A uri without port is allowed, in which case the port will be inferred from the scheme. {@code http} is 80, and
* {@code https} is 443.
* </p>
* <p>
* The host of the {@code uri} should not be null, or it will be treated as an illegal param,
* and an {@link IllegalArgumentException} will be thrown.
* </p>
*/
public static IpPort parseIpPort(URI uri) {
return parseIpPort(uri, false);
}
/**
* Parse a {@link URI} into an {@link IpPort}
* @param uri a uri representing {@link IpPort}
* @param ignorePortUndefined whether the port should be inferred from scheme, when the port part of {@code uri} is {@code -1}.
* If {@code true} the undefined port is ignored;
* otherwise a port will be inferred from scheme: {@code http} is 80, and {@code https} is 443.
*/
public static IpPort parseIpPort(URI uri, boolean ignorePortUndefined) {
if (null == uri.getHost()) {
// if the format of address is legal but the value is out of range, URI#create(String) will not throw exception
// but return a URI with null host.
throw new IllegalArgumentException("Illegal uri: [" + uri + "]");
}
IpPort ipPort = new IpPort(uri.getHost(), uri.getPort());
if (-1 != ipPort.getPort() || ignorePortUndefined) {
return ipPort;
}
if (uri.getScheme().equals("http")) {
ipPort.setPort(80);
}
if (uri.getScheme().equals("https")) {
ipPort.setPort(443);
}
return ipPort;
}
/**
* @param uriAddress the address containing IP and port info.
* @return IpPort parsed from input param, or {@code null} if the param is null.
*/
public static IpPort parseIpPortFromURI(String uriAddress) {
if (uriAddress == null) {
return null;
}
try {
return parseIpPort(new URI(uriAddress));
} catch (URISyntaxException e) {
return null;
}
}
public static IpPort parseIpPort(String scheme, String authority) {
if (authority == null) {
return null;
}
return parseIpPort(URI.create(scheme + "://" + authority));
}
/**
* 对于配置为0.0.0.0的地址,let it go
* schema, e.g. http
* adddress, e.g 0.0.0.0:8080
* return 实际监听的地址
*/
public static String getRealListenAddress(String schema, String address) {
if (address == null) {
return null;
}
try {
URI originalURI = new URI(schema + "://" + address);
// validate original url
NetUtils.parseIpPort(originalURI);
return originalURI.toString();
} catch (URISyntaxException e) {
LOGGER.error("address {} is not valid.", address);
return null;
}
}
public static String getHostName() {
//If failed to get host name ,micro-service will registry failed
//So I add retry mechanism
if (hostName == null) {
doGetHostNameAndHostAddress();
}
return hostName;
}
@VisibleForTesting
static void resetHostName() {
hostName = null;
}
public static String getHostAddress() {
//If failed to get host address ,micro-service will registry failed
//So I add retry mechanism
if (hostAddress == null) {
doGetHostNameAndHostAddress();
}
return hostAddress;
}
public static String getIpv6HostAddress() {
//If failed to get host address ,micro-service will registry failed
//So I add retry mechanism
if (hostAddressIpv6 == null) {
doGetHostNameAndHostAddress();
}
return hostAddressIpv6;
}
public static InetAddress getInterfaceAddress(String interfaceName) {
return allInterfaceAddresses.get(interfaceName);
}
public static InetAddress ensureGetInterfaceAddress(String interfaceName) {
InetAddress address = allInterfaceAddresses.get(interfaceName);
if (address == null) {
throw new IllegalArgumentException("Can not find address for interface name: " + interfaceName);
}
return address;
}
@SuppressWarnings({"unused", "try"})
public static boolean canTcpListen(InetAddress address, int port) {
try (ServerSocket ss = new ServerSocket(port, 0, address)) {
return true;
} catch (IOException e) {
return false;
}
}
public static String humanReadableBytes(long bytes) {
int unit = 1024;
if (bytes < unit) {
return String.valueOf(bytes);
}
int exp = (int) (Math.log(bytes) / Math.log(unit));
char pre = "KMGTPE" .charAt(exp - 1);
return String.format("%.3f%c", bytes / Math.pow(unit, exp), pre);
}
}