From cd7b7590ac7ab7457937b1c0f4b68cd9f374450b Mon Sep 17 00:00:00 2001 From: qinliujie Date: Wed, 14 Sep 2022 14:15:19 +0800 Subject: [PATCH] Ipv6 Support (#10603) --- .../common/constants/CommonConstants.java | 2 + .../common/url/component/URLAddress.java | 4 +- .../apache/dubbo/common/utils/NetUtils.java | 94 ++++++++++++++++++- .../apache/dubbo/config/AbstractConfig.java | 3 +- .../apache/dubbo/common/URLStrParserTest.java | 1 + .../java/org/apache/dubbo/common/URLTest.java | 10 +- .../apache/dubbo/config/ServiceConfig.java | 10 ++ .../metadata/DefaultMetadataParamsFilter.java | 6 +- .../integration/RegistryProtocol.java | 3 +- .../transport/netty4/NettyClient.java | 32 ++++++- .../dubbo/rpc/filter/ContextFilter.java | 4 +- 11 files changed, 156 insertions(+), 13 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java index 321d8e2fb5..bd4e256528 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java @@ -392,6 +392,8 @@ public interface CommonConstants { String PROTOCOL_SERVER = "server"; + String IPV6_KEY = "ipv6"; + /** * The parameter key for the class path of the ServiceNameMapping {@link Properties} file * diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLAddress.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLAddress.java index 2695f2afa1..0362af72de 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLAddress.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLAddress.java @@ -86,7 +86,7 @@ public class URLAddress implements Serializable { } public URLAddress setHost(String host) { - return new URLAddress(host, port, rawAddress); + return new URLAddress(host, port, null); } public int getPort() { @@ -94,7 +94,7 @@ public class URLAddress implements Serializable { } public URLAddress setPort(int port) { - return new URLAddress(host, port, rawAddress); + return new URLAddress(host, port, null); } public String getAddress() { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java index 473b8366b1..43656f05b0 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java @@ -96,6 +96,7 @@ public final class NetUtils { private static final Map 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 = ":"; @@ -250,6 +251,8 @@ public final class NetUtils { private static volatile String HOST_ADDRESS; + private static volatile String HOST_ADDRESS_V6; + public static String getLocalHost() { if (HOST_ADDRESS != null) { return HOST_ADDRESS; @@ -257,6 +260,15 @@ public final class NetUtils { 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; } @@ -264,6 +276,29 @@ public final class NetUtils { 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; @@ -309,6 +344,15 @@ public final class NetUtils { 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 toValidAddress(InetAddress address) { if (address instanceof Inet6Address) { Inet6Address v6Address = (Inet6Address) address; @@ -355,10 +399,34 @@ public final class NetUtils { 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 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. * @@ -380,7 +448,7 @@ public final class NetUtils { for (String ignoredInterface : ignoredInterfaces.split(",")) { String trimIgnoredInterface = ignoredInterface.trim(); boolean matched = false; - try { + try { matched = networkInterfaceDisplayName.matches(trimIgnoredInterface); } catch (PatternSyntaxException e) { // if trimIgnoredInterface is an invalid regular expression, a PatternSyntaxException will be thrown out @@ -744,4 +812,28 @@ public final class NetUtils { 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; + } + } + } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java index 09bc1a44e7..9996c919c3 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java @@ -52,7 +52,6 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -364,7 +363,7 @@ public abstract class AbstractConfig implements Serializable { */ protected static Map convert(Map parameters, String prefix) { if (parameters == null || parameters.isEmpty()) { - return Collections.emptyMap(); + return new HashMap<>(); } Map result = new HashMap<>(); diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java index f295de9f57..fdd3533361 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java @@ -43,6 +43,7 @@ public class URLStrParserTest { testCases.add("dubbo://192.168.1.1/" + RandomString.make(10240)); testCases.add("file:/path/to/file.txt"); testCases.add("dubbo://fe80:0:0:0:894:aeec:f37d:23e1%en0/path?abc=abc"); + testCases.add("dubbo://[fe80:0:0:0:894:aeec:f37d:23e1]:20880/path?abc=abc"); errorDecodedCases.add("dubbo:192.168.1.1"); errorDecodedCases.add("://192.168.1.1"); diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java index e78ab5d80c..033901bd89 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java @@ -18,7 +18,6 @@ package org.apache.dubbo.common; import org.apache.dubbo.common.url.component.ServiceConfigURL; import org.apache.dubbo.common.utils.CollectionUtils; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -1082,4 +1081,13 @@ public class URLTest { URL urlWithoutUserInformation = URL.valueOf("10.20.130.230:20880/context/path?version=1.0.0&application=app1"); assertEquals("10.20.130.230:20880", urlWithoutUserInformation.getAuthority()); } + + + @Test + public void testIPV6() { + URL url = URL.valueOf("dubbo://[2408:4004:194:8896:3e8a:82ae:814a:398]:20881?name=apache"); + assertEquals("[2408:4004:194:8896:3e8a:82ae:814a:398]", url.getHost()); + assertEquals(20881, url.getPort()); + assertEquals("apache", url.getParameter("name")); + } } diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java index 35a8cd4906..833184fbd1 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java @@ -28,6 +28,7 @@ import org.apache.dubbo.common.url.component.ServiceConfigURL; import org.apache.dubbo.common.utils.ClassUtils; import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.common.utils.ConfigUtils; +import org.apache.dubbo.common.utils.NetUtils; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.config.annotation.Service; import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker; @@ -554,6 +555,15 @@ public class ServiceConfig extends ServiceConfigBase { // export service String host = findConfiguredHosts(protocolConfig, provider, params); + if (NetUtils.isIPV6URLStdFormat(host)) { + if (!host.contains("[")) { + host = "[" + host + "]"; + } + } else if (NetUtils.getLocalHostV6() != null) { + String ipv6Host = NetUtils.getLocalHostV6(); + params.put(CommonConstants.IPV6_KEY, ipv6Host); + } + Integer port = findConfiguredPort(protocolConfig, provider, this.getExtensionLoader(Protocol.class), name, params); URL url = new ServiceConfigURL(name, null, null, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), params); diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java index 7687b04026..241ba8c839 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java @@ -18,6 +18,7 @@ package org.apache.dubbo.metadata; import org.apache.dubbo.common.extension.Activate; +import static org.apache.dubbo.common.constants.CommonConstants.IPV6_KEY; import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY; @@ -37,9 +38,10 @@ public class DefaultMetadataParamsFilter implements MetadataParamsFilter { private final String[] includedInstanceParams; public DefaultMetadataParamsFilter() { - this.includedInstanceParams = new String[]{HEARTBEAT_TIMEOUT_KEY, TIMESTAMP_KEY}; + this.includedInstanceParams = new String[]{HEARTBEAT_TIMEOUT_KEY, TIMESTAMP_KEY, IPV6_KEY}; this.excludedServiceParams = new String[]{MONITOR_KEY, BIND_IP_KEY, BIND_PORT_KEY, QOS_ENABLE, - QOS_HOST, QOS_PORT, ACCEPT_FOREIGN_IP, VALIDATION_KEY, INTERFACES, PID_KEY, TIMESTAMP_KEY, HEARTBEAT_TIMEOUT_KEY}; + QOS_HOST, QOS_PORT, ACCEPT_FOREIGN_IP, VALIDATION_KEY, INTERFACES, PID_KEY, TIMESTAMP_KEY, HEARTBEAT_TIMEOUT_KEY, + IPV6_KEY}; } @Override diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java index 913c0cc539..05387f55a7 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java @@ -78,6 +78,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.EXTRA_KEYS_KEY; import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; import static org.apache.dubbo.common.constants.CommonConstants.HIDE_KEY_PREFIX; import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.IPV6_KEY; import static org.apache.dubbo.common.constants.CommonConstants.LOADBALANCE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; @@ -136,7 +137,7 @@ public class RegistryProtocol implements Protocol, ScopeModelAware { public static final String[] DEFAULT_REGISTER_PROVIDER_KEYS = { APPLICATION_KEY, CODEC_KEY, EXCHANGER_KEY, SERIALIZATION_KEY, CLUSTER_KEY, CONNECTIONS_KEY, DEPRECATED_KEY, GROUP_KEY, LOADBALANCE_KEY, MOCK_KEY, PATH_KEY, TIMEOUT_KEY, TOKEN_KEY, VERSION_KEY, WARMUP_KEY, - WEIGHT_KEY, DUBBO_VERSION_KEY, RELEASE_KEY, SIDE_KEY + WEIGHT_KEY, DUBBO_VERSION_KEY, RELEASE_KEY, SIDE_KEY, IPV6_KEY }; public static final String[] DEFAULT_REGISTER_CONSUMER_KEYS = { diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java index 1f0c3f7199..025e902779 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java @@ -19,6 +19,7 @@ package org.apache.dubbo.remoting.transport.netty4; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.Version; import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.resource.GlobalResourceInitializer; @@ -149,8 +150,33 @@ public class NettyClient extends AbstractClient { @Override protected void doConnect() throws Throwable { + try { + String ipv6Address = NetUtils.getLocalHostV6(); + InetSocketAddress connectAddress; + //first try ipv6 address + if (ipv6Address != null && getUrl().getParameter(CommonConstants.IPV6_KEY) != null) { + connectAddress = new InetSocketAddress(getUrl().getParameter(CommonConstants.IPV6_KEY), getUrl().getPort()); + try { + doConnect(connectAddress); + return; + } catch (Throwable throwable) { + //ignore + } + } + + connectAddress = getConnectAddress(); + doConnect(connectAddress); + } finally { + // just add new valid channel to NettyChannel's cache + if (!isConnected()) { + //future.cancel(true); + } + } + } + + private void doConnect(InetSocketAddress serverAddress) throws RemotingException { long start = System.currentTimeMillis(); - ChannelFuture future = bootstrap.connect(getConnectAddress()); + ChannelFuture future = bootstrap.connect(serverAddress); try { boolean ret = future.awaitUninterruptibly(getConnectTimeout(), MILLISECONDS); @@ -192,7 +218,7 @@ public class NettyClient extends AbstractClient { // 6-1 Failed to connect to provider server by other reason. RemotingException remotingException = new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server " - + getRemoteAddress() + ", error message is:" + cause.getMessage(), cause); + + serverAddress + ", error message is:" + cause.getMessage(), cause); logger.error("6-1", "network disconnected", "", "Failed to connect to provider server by other reason.", cause); @@ -204,7 +230,7 @@ public class NettyClient extends AbstractClient { // 6-2 Client-side timeout RemotingException remotingException = new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server " - + getRemoteAddress() + " client-side timeout " + + serverAddress + " client-side timeout " + getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client " + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()); diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java index 356995d864..6fc453aee9 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java @@ -97,7 +97,9 @@ public class ContextFilter implements Filter, Filter.Listener { RpcContext context = RpcContext.getServerAttachment(); // .setAttachments(attachments) // merged from dubbox - context.setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); + if (context.getLocalAddress() == null) { + context.setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); + } String remoteApplication = invocation.getAttachment(REMOTE_APPLICATION_KEY); if (StringUtils.isNotEmpty(remoteApplication)) {