blob: 43656f05b06a35127acffc5686511f86722d694d [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.dubbo.common.utils;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.ConfigurationUtils;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.logger.support.FailsafeLogger;
import org.apache.dubbo.rpc.model.ScopeModel;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.BitSet;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import static java.util.Collections.emptyList;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_IP_TO_BIND;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_NETWORK_IGNORED_INTERFACE;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PREFERRED_NETWORK_INTERFACE;
import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
import static org.apache.dubbo.common.utils.CollectionUtils.first;
/**
* IP and Port Helper for RPC
*/
public final class NetUtils {
/**
* Forbids instantiation.
*/
private NetUtils() {
throw new UnsupportedOperationException("No instance of 'NetUtils' for you! ");
}
private static Logger logger;
static {
/*
DO NOT replace this logger to error type aware logger (or fail-safe logger), since its
logging method calls NetUtils.getLocalHost().
According to issue #4992, getLocalHost() method will be endless recursively invoked when network disconnected.
*/
logger = LoggerFactory.getLogger(NetUtils.class);
if (logger instanceof FailsafeLogger) {
logger = ((FailsafeLogger) logger).getLogger();
}
}
// returned port range is [30000, 39999]
private static final int RND_PORT_START = 30000;
private static final int RND_PORT_RANGE = 10000;
// valid port range is (0, 65535]
private static final int MIN_PORT = 1;
private static final int MAX_PORT = 65535;
private static final Pattern ADDRESS_PATTERN = Pattern.compile("^\\d{1,3}(\\.\\d{1,3}){3}\\:\\d{1,5}$");
private static final Pattern LOCAL_IP_PATTERN = Pattern.compile("127(\\.\\d{1,3}){3}$");
private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
private static final Map<String, String> HOST_NAME_CACHE = new LRUCache<>(1000);
private static volatile InetAddress LOCAL_ADDRESS = null;
private static volatile Inet6Address LOCAL_ADDRESS_V6 = null;
private static final String SPLIT_IPV4_CHARACTER = "\\.";
private static final String SPLIT_IPV6_CHARACTER = ":";
/**
* store the used port.
* the set used only on the synchronized method.
*/
private static BitSet USED_PORT = new BitSet(65536);
public static int getRandomPort() {
return RND_PORT_START + ThreadLocalRandom.current().nextInt(RND_PORT_RANGE);
}
public static synchronized int getAvailablePort() {
int randomPort = getRandomPort();
return getAvailablePort(randomPort);
}
public static synchronized int getAvailablePort(int port) {
if (port < MIN_PORT) {
return MIN_PORT;
}
for (int i = port; i < MAX_PORT; i++) {
if (USED_PORT.get(i)) {
continue;
}
try (ServerSocket ignored = new ServerSocket(i)) {
USED_PORT.set(i);
port = i;
break;
} catch (IOException e) {
// continue
}
}
return port;
}
/**
* Check the port whether is in use in os
* @param port port to check
* @return true if it's occupied
*/
public static boolean isPortInUsed(int port) {
try (ServerSocket ignored = new ServerSocket(port)) {
return false;
} catch (IOException e) {
// continue
}
return true;
}
/**
* Tells whether the port to test is an invalid port.
*
* @implNote Numeric comparison only.
* @param port port to test
* @return true if invalid
*/
public static boolean isInvalidPort(int port) {
return port < MIN_PORT || port > MAX_PORT;
}
/**
* Tells whether the address to test is an invalid address.
*
* @implNote Pattern matching only.
* @param address address to test
* @return true if invalid
*/
public static boolean isValidAddress(String address) {
return ADDRESS_PATTERN.matcher(address).matches();
}
public static boolean isLocalHost(String host) {
return host != null
&& (LOCAL_IP_PATTERN.matcher(host).matches()
|| host.equalsIgnoreCase(LOCALHOST_KEY));
}
public static boolean isAnyHost(String host) {
return ANYHOST_VALUE.equals(host);
}
public static boolean isInvalidLocalHost(String host) {
return host == null
|| host.length() == 0
|| host.equalsIgnoreCase(LOCALHOST_KEY)
|| host.equals(ANYHOST_VALUE)
|| host.startsWith("127.");
}
public static boolean isValidLocalHost(String host) {
return !isInvalidLocalHost(host);
}
public static InetSocketAddress getLocalSocketAddress(String host, int port) {
return isInvalidLocalHost(host) ?
new InetSocketAddress(port) : new InetSocketAddress(host, port);
}
static boolean isValidV4Address(InetAddress address) {
if (address == null || address.isLoopbackAddress()) {
return false;
}
String name = address.getHostAddress();
return (name != null
&& IP_PATTERN.matcher(name).matches()
&& !ANYHOST_VALUE.equals(name)
&& !LOCALHOST_VALUE.equals(name));
}
/**
* Check if an ipv6 address
*
* @return true if it is reachable
*/
static boolean isPreferIPV6Address() {
return Boolean.getBoolean("java.net.preferIPv6Addresses");
}
/**
* normalize the ipv6 Address, convert scope name to scope id.
* e.g.
* convert
* fe80:0:0:0:894:aeec:f37d:23e1%en0
* to
* fe80:0:0:0:894:aeec:f37d:23e1%5
* <p>
* The %5 after ipv6 address is called scope id.
* see java doc of {@link Inet6Address} for more details.
*
* @param address the input address
* @return the normalized address, with scope id converted to int
*/
static InetAddress normalizeV6Address(Inet6Address address) {
String addr = address.getHostAddress();
int i = addr.lastIndexOf('%');
if (i > 0) {
try {
return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId());
} catch (UnknownHostException e) {
// ignore
logger.debug("Unknown IPV6 address: ", e);
}
}
return address;
}
private static volatile String HOST_ADDRESS;
private static volatile String HOST_ADDRESS_V6;
public static String getLocalHost() {
if (HOST_ADDRESS != null) {
return HOST_ADDRESS;
}
InetAddress address = getLocalAddress();
if (address != null) {
if (address instanceof Inet6Address) {
String ipv6AddressString = address.getHostAddress();
if (ipv6AddressString.contains("%")) {
ipv6AddressString = ipv6AddressString.substring(0, ipv6AddressString.indexOf("%"));
}
HOST_ADDRESS = ipv6AddressString;
return HOST_ADDRESS;
}
HOST_ADDRESS = address.getHostAddress();
return HOST_ADDRESS;
}
return LOCALHOST_VALUE;
}
public static String getLocalHostV6() {
if (StringUtils.isNotEmpty(HOST_ADDRESS_V6)) {
return HOST_ADDRESS_V6;
}
//avoid to search network interface card many times
if("".equals(HOST_ADDRESS_V6)){
return null;
}
Inet6Address address = getLocalAddressV6();
if (address != null) {
String ipv6AddressString = address.getHostAddress();
if (ipv6AddressString.contains("%")) {
ipv6AddressString = ipv6AddressString.substring(0, ipv6AddressString.indexOf("%"));
}
HOST_ADDRESS_V6 = ipv6AddressString;
return HOST_ADDRESS_V6;
}
HOST_ADDRESS_V6 = "";
return null;
}
public static String filterLocalHost(String host) {
if (host == null || host.length() == 0) {
return host;
}
if (host.contains("://")) {
URL u = URL.valueOf(host);
if (NetUtils.isInvalidLocalHost(u.getHost())) {
return u.setHost(NetUtils.getLocalHost()).toFullString();
}
} else if (host.contains(":")) {
int i = host.lastIndexOf(':');
if (NetUtils.isInvalidLocalHost(host.substring(0, i))) {
return NetUtils.getLocalHost() + host.substring(i);
}
} else {
if (NetUtils.isInvalidLocalHost(host)) {
return NetUtils.getLocalHost();
}
}
return host;
}
public static String getIpByConfig(ScopeModel scopeModel) {
String configIp = ConfigurationUtils.getProperty(scopeModel, DUBBO_IP_TO_BIND);
if (configIp != null) {
return configIp;
}
return getLocalHost();
}
/**
* Find first valid IP from local network card
*
* @return first valid local IP
*/
public static InetAddress getLocalAddress() {
if (LOCAL_ADDRESS != null) {
return LOCAL_ADDRESS;
}
InetAddress localAddress = getLocalAddress0();
LOCAL_ADDRESS = localAddress;
return localAddress;
}
public static Inet6Address getLocalAddressV6() {
if (LOCAL_ADDRESS_V6 != null) {
return LOCAL_ADDRESS_V6;
}
Inet6Address localAddress = getLocalAddress0V6();
LOCAL_ADDRESS_V6 = localAddress;
return localAddress;
}
private static Optional<InetAddress> toValidAddress(InetAddress address) {
if (address instanceof Inet6Address) {
Inet6Address v6Address = (Inet6Address) address;
if (isPreferIPV6Address()) {
return Optional.ofNullable(normalizeV6Address(v6Address));
}
}
if (isValidV4Address(address)) {
return Optional.of(address);
}
return Optional.empty();
}
private static InetAddress getLocalAddress0() {
InetAddress localAddress = null;
// @since 2.7.6, choose the {@link NetworkInterface} first
try {
NetworkInterface networkInterface = findNetworkInterface();
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
if (addressOp.isPresent()) {
try {
if (addressOp.get().isReachable(100)) {
return addressOp.get();
}
} catch (IOException e) {
// ignore
}
}
}
} catch (Throwable e) {
logger.warn(e);
}
try {
localAddress = InetAddress.getLocalHost();
Optional<InetAddress> addressOp = toValidAddress(localAddress);
if (addressOp.isPresent()) {
return addressOp.get();
}
} catch (Throwable e) {
logger.warn(e);
}
localAddress = getLocalAddressV6();
return localAddress;
}
private static Inet6Address getLocalAddress0V6() {
// @since 2.7.6, choose the {@link NetworkInterface} first
try {
NetworkInterface networkInterface = findNetworkInterface();
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if (address instanceof Inet6Address) {
if (!address.isLoopbackAddress() //filter 127.x.x.x
&& !address.isAnyLocalAddress() // filter 0.0.0.0
&& !address.isLinkLocalAddress() //filter 169.254.0.0/16
&& address.getHostAddress().contains(":")) {//filter IPv6
return (Inet6Address) address;
}
}
}
} catch (Throwable e) {
logger.warn(e);
}
return null;
}
/**
* Returns {@code true} if the specified {@link NetworkInterface} should be ignored with the given conditions.
*
* @param networkInterface the {@link NetworkInterface} to check
* @return {@code true} if the specified {@link NetworkInterface} should be ignored, otherwise {@code false}
* @throws SocketException SocketException if an I/O error occurs.
*/
private static boolean ignoreNetworkInterface(NetworkInterface networkInterface) throws SocketException {
if (networkInterface == null
|| networkInterface.isLoopback()
|| networkInterface.isVirtual()
|| !networkInterface.isUp()) {
return true;
}
String ignoredInterfaces = System.getProperty(DUBBO_NETWORK_IGNORED_INTERFACE);
String networkInterfaceDisplayName;
if (StringUtils.isNotEmpty(ignoredInterfaces)
&& StringUtils.isNotEmpty(networkInterfaceDisplayName = networkInterface.getDisplayName())) {
for (String ignoredInterface : ignoredInterfaces.split(",")) {
String trimIgnoredInterface = ignoredInterface.trim();
boolean matched = false;
try {
matched = networkInterfaceDisplayName.matches(trimIgnoredInterface);
} catch (PatternSyntaxException e) {
// if trimIgnoredInterface is an invalid regular expression, a PatternSyntaxException will be thrown out
logger.warn("exception occurred: " + networkInterfaceDisplayName + " matches " + trimIgnoredInterface, e);
} finally {
if (matched) {
return true;
}
if (networkInterfaceDisplayName.equals(trimIgnoredInterface)) {
return true;
}
}
}
}
return false;
}
/**
* Get the valid {@link NetworkInterface network interfaces}
*
* @return the valid {@link NetworkInterface}s
* @throws SocketException SocketException if an I/O error occurs.
* @since 2.7.6
*/
private static List<NetworkInterface> getValidNetworkInterfaces() throws SocketException {
List<NetworkInterface> validNetworkInterfaces = new LinkedList<>();
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
if (ignoreNetworkInterface(networkInterface)) { // ignore
continue;
}
validNetworkInterfaces.add(networkInterface);
}
return validNetworkInterfaces;
}
/**
* Is preferred {@link NetworkInterface} or not
*
* @param networkInterface {@link NetworkInterface}
* @return if the name of the specified {@link NetworkInterface} matches
* the property value from {@link CommonConstants#DUBBO_PREFERRED_NETWORK_INTERFACE}, return <code>true</code>,
* or <code>false</code>
*/
public static boolean isPreferredNetworkInterface(NetworkInterface networkInterface) {
String preferredNetworkInterface = System.getProperty(DUBBO_PREFERRED_NETWORK_INTERFACE);
return Objects.equals(networkInterface.getDisplayName(), preferredNetworkInterface);
}
/**
* Get the suitable {@link NetworkInterface}
*
* @return If no {@link NetworkInterface} is available , return <code>null</code>
* @since 2.7.6
*/
public static NetworkInterface findNetworkInterface() {
List<NetworkInterface> validNetworkInterfaces = emptyList();
try {
validNetworkInterfaces = getValidNetworkInterfaces();
} catch (Throwable e) {
logger.warn(e);
}
NetworkInterface result = null;
// Try to find the preferred one
for (NetworkInterface networkInterface : validNetworkInterfaces) {
if (isPreferredNetworkInterface(networkInterface)) {
result = networkInterface;
break;
}
}
if (result == null) { // If not found, try to get the first one
for (NetworkInterface networkInterface : validNetworkInterfaces) {
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
if (addressOp.isPresent()) {
try {
if (addressOp.get().isReachable(100)) {
return networkInterface;
}
} catch (IOException e) {
// ignore
}
}
}
}
}
if (result == null) {
result = first(validNetworkInterfaces);
}
return result;
}
public static String getHostName(String address) {
try {
int i = address.indexOf(':');
if (i > -1) {
address = address.substring(0, i);
}
String hostname = HOST_NAME_CACHE.get(address);
if (hostname != null && hostname.length() > 0) {
return hostname;
}
InetAddress inetAddress = InetAddress.getByName(address);
if (inetAddress != null) {
hostname = inetAddress.getHostName();
HOST_NAME_CACHE.put(address, hostname);
return hostname;
}
} catch (Throwable e) {
// ignore
}
return address;
}
public static String getLocalHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
return getLocalAddress().getHostName();
}
}
/**
* @param hostName
* @return ip address or hostName if UnknownHostException
*/
public static String getIpByHost(String hostName) {
try {
return InetAddress.getByName(hostName).getHostAddress();
} catch (UnknownHostException e) {
return hostName;
}
}
public static String toAddressString(InetSocketAddress address) {
return address.getAddress().getHostAddress() + ":" + address.getPort();
}
public static InetSocketAddress toAddress(String address) {
int i = address.indexOf(':');
String host;
int port;
if (i > -1) {
host = address.substring(0, i);
port = Integer.parseInt(address.substring(i + 1));
} else {
host = address;
port = 0;
}
return new InetSocketAddress(host, port);
}
public static String toURL(String protocol, String host, int port, String path) {
StringBuilder sb = new StringBuilder();
sb.append(protocol).append("://");
sb.append(host).append(':').append(port);
if (path.charAt(0) != '/') {
sb.append('/');
}
sb.append(path);
return sb.toString();
}
@SuppressWarnings("deprecation")
public static void joinMulticastGroup(MulticastSocket multicastSocket, InetAddress multicastAddress) throws
IOException {
setInterface(multicastSocket, multicastAddress instanceof Inet6Address);
// For the deprecation notice: the equivalent only appears in JDK 9+.
multicastSocket.setLoopbackMode(false);
multicastSocket.joinGroup(multicastAddress);
}
@SuppressWarnings("deprecation")
public static void setInterface(MulticastSocket multicastSocket, boolean preferIpv6) throws IOException {
boolean interfaceSet = false;
for (NetworkInterface networkInterface : getValidNetworkInterfaces()) {
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if (preferIpv6 && address instanceof Inet6Address) {
try {
if (address.isReachable(100)) {
multicastSocket.setInterface(address);
interfaceSet = true;
break;
}
} catch (IOException e) {
// ignore
}
} else if (!preferIpv6 && address instanceof Inet4Address) {
try {
if (address.isReachable(100)) {
multicastSocket.setInterface(address);
interfaceSet = true;
break;
}
} catch (IOException e) {
// ignore
}
}
}
if (interfaceSet) {
break;
}
}
}
public static boolean matchIpExpression(String pattern, String host, int port) throws UnknownHostException {
// if the pattern is subnet format, it will not be allowed to config port param in pattern.
if (pattern.contains("/")) {
CIDRUtils utils = new CIDRUtils(pattern);
return utils.isInRange(host);
}
return matchIpRange(pattern, host, port);
}
/**
* @param pattern
* @param host
* @param port
* @return
* @throws UnknownHostException
*/
public static boolean matchIpRange(String pattern, String host, int port) throws UnknownHostException {
if (pattern == null || host == null) {
throw new IllegalArgumentException("Illegal Argument pattern or hostName. Pattern:" + pattern + ", Host:" + host);
}
pattern = pattern.trim();
if ("*.*.*.*".equals(pattern) || "*".equals(pattern)) {
return true;
}
InetAddress inetAddress = InetAddress.getByName(host);
boolean isIpv4 = isValidV4Address(inetAddress);
String[] hostAndPort = getPatternHostAndPort(pattern, isIpv4);
if (hostAndPort[1] != null && !hostAndPort[1].equals(String.valueOf(port))) {
return false;
}
pattern = hostAndPort[0];
String splitCharacter = SPLIT_IPV4_CHARACTER;
if (!isIpv4) {
splitCharacter = SPLIT_IPV6_CHARACTER;
}
String[] mask = pattern.split(splitCharacter);
// check format of pattern
checkHostPattern(pattern, mask, isIpv4);
host = inetAddress.getHostAddress();
if (pattern.equals(host)) {
return true;
}
// short name condition
if (!ipPatternContainExpression(pattern)) {
InetAddress patternAddress = InetAddress.getByName(pattern);
return patternAddress.getHostAddress().equals(host);
}
String[] ipAddress = host.split(splitCharacter);
for (int i = 0; i < mask.length; i++) {
if ("*".equals(mask[i]) || mask[i].equals(ipAddress[i])) {
continue;
} else if (mask[i].contains("-")) {
String[] rangeNumStrs = StringUtils.split(mask[i], '-');
if (rangeNumStrs.length != 2) {
throw new IllegalArgumentException("There is wrong format of ip Address: " + mask[i]);
}
Integer min = getNumOfIpSegment(rangeNumStrs[0], isIpv4);
Integer max = getNumOfIpSegment(rangeNumStrs[1], isIpv4);
Integer ip = getNumOfIpSegment(ipAddress[i], isIpv4);
if (ip < min || ip > max) {
return false;
}
} else if ("0".equals(ipAddress[i]) && ("0".equals(mask[i]) || "00".equals(mask[i]) || "000".equals(mask[i]) || "0000".equals(mask[i]))) {
continue;
} else if (!mask[i].equals(ipAddress[i])) {
return false;
}
}
return true;
}
/**
* is multicast address or not
*
* @param host ipv4 address
* @return {@code true} if is multicast address
*/
public static boolean isMulticastAddress(String host) {
int i = host.indexOf('.');
if (i > 0) {
String prefix = host.substring(0, i);
if (StringUtils.isNumber(prefix)) {
int p = Integer.parseInt(prefix);
return p >= 224 && p <= 239;
}
}
return false;
}
private static boolean ipPatternContainExpression(String pattern) {
return pattern.contains("*") || pattern.contains("-");
}
private static void checkHostPattern(String pattern, String[] mask, boolean isIpv4) {
if (!isIpv4) {
if (mask.length != 8 && ipPatternContainExpression(pattern)) {
throw new IllegalArgumentException("If you config ip expression that contains '*' or '-', please fill qualified ip pattern like 234e:0:4567:0:0:0:3d:*. ");
}
if (mask.length != 8 && !pattern.contains("::")) {
throw new IllegalArgumentException("The host is ipv6, but the pattern is not ipv6 pattern : " + pattern);
}
} else {
if (mask.length != 4) {
throw new IllegalArgumentException("The host is ipv4, but the pattern is not ipv4 pattern : " + pattern);
}
}
}
private static String[] getPatternHostAndPort(String pattern, boolean isIpv4) {
String[] result = new String[2];
if (pattern.startsWith("[") && pattern.contains("]:")) {
int end = pattern.indexOf("]:");
result[0] = pattern.substring(1, end);
result[1] = pattern.substring(end + 2);
return result;
} else if (pattern.startsWith("[") && pattern.endsWith("]")) {
result[0] = pattern.substring(1, pattern.length() - 1);
result[1] = null;
return result;
} else if (isIpv4 && pattern.contains(":")) {
int end = pattern.indexOf(":");
result[0] = pattern.substring(0, end);
result[1] = pattern.substring(end + 1);
return result;
} else {
result[0] = pattern;
return result;
}
}
private static Integer getNumOfIpSegment(String ipSegment, boolean isIpv4) {
if (isIpv4) {
return Integer.parseInt(ipSegment);
}
return Integer.parseInt(ipSegment, 16);
}
public static boolean isIPV6URLStdFormat(String ip) {
if ((ip.charAt(0) == '[' && ip.indexOf(']') > 2)) {
return true;
} else if (ip.indexOf(":") != ip.lastIndexOf(":")) {
return true;
} else {
return false;
}
}
public static String getLegalIP(String ip) {
//ipv6 [::FFFF:129.144.52.38]:80
int ind;
if ((ip.charAt(0) == '[' && (ind = ip.indexOf(']')) > 2)) {
String nhost = ip;
ip = nhost.substring(0, ind + 1);
ip = ip.substring(1, ind);
return ip;
} else {
return ip;
}
}
}