Ipv6 Support (#10603)

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 321d8e2..bd4e256 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 @@
 
     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 2695f2a..0362af7 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 URLAddress setHost(String host) {
-        return new URLAddress(host, port, rawAddress);
+        return new URLAddress(host, port, null);
     }
 
     public int getPort() {
@@ -94,7 +94,7 @@
     }
 
     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 473b836..43656f0 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 @@
 
     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 = ":";
@@ -250,6 +251,8 @@
 
     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 @@
 
         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 @@
         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 @@
         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;
@@ -355,10 +399,34 @@
             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.
      *
@@ -380,7 +448,7 @@
             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 @@
         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 09bc1a4..9996c91 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.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 @@
      */
     protected static Map<String, String> convert(Map<String, String> parameters, String prefix) {
         if (parameters == null || parameters.isEmpty()) {
-            return Collections.emptyMap();
+            return new HashMap<>();
         }
 
         Map<String, String> 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 f295de9..fdd3533 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 @@
         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 e78ab5d..033901b 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 @@
 
 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 @@
         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 35a8cd4..833184f 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.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 @@
 
         // 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 7687b04..241ba8c 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 @@
 
 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 @@
     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 913c0cc..05387f5 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.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 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 1f0c3f7..025e902 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 @@
 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 @@
 
     @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 @@
                 // 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 @@
                 // 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 356995d..6fc453a 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 @@
 
         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)) {