Skip to content

Commit

Permalink
[Dubbo-2064] Ipv6 support (#2079)
Browse files Browse the repository at this point in the history
* Url support ipv6 address.

* Unwrap if statement, since returned Enumeration can't be null. No functional change.

* Ensure Dubbo provider/consumer both works under ipv6 multicast network.

* Fix typo.

* Fix typo.

* Disable mocking final class feature of mockito to avoid UT failure.

* Add null check for NetworkInterface.getNetworkInterfaces()

* Improve UT.

* Improve UT.
  • Loading branch information
ralf0131 authored and zonghaishang committed Aug 2, 2018
1 parent c904589 commit 413a2a0
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 85 deletions.
13 changes: 10 additions & 3 deletions dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,17 @@ public static URL valueOf(String url) {
}
url = url.substring(i + 1);
}
i = url.indexOf(":");
i = url.lastIndexOf(":");
if (i >= 0 && i < url.length() - 1) {
port = Integer.parseInt(url.substring(i + 1));
url = url.substring(0, i);
if (url.lastIndexOf("%") > i) {
// ipv6 address with scope id
// e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
// see https://howdoesinternetwork.com/2013/ipv6-zone-id
// ignore
} else {
port = Integer.parseInt(url.substring(i+1));
url = url.substring(0, i);
}
}
if (url.length() > 0) host = url;
return new URL(protocol, username, password, host, port, path, parameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.dubbo.common.logger.LoggerFactory;

import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
Expand Down Expand Up @@ -142,6 +143,51 @@ static boolean isValidAddress(InetAddress address) {
&& IP_PATTERN.matcher(name).matches());
}

/**
* Check if an ipv6 address is reachable.
* @param address the given address
* @return true if it is reachable
*/
static boolean isValidV6Address(Inet6Address address) {
boolean preferIpv6 = Boolean.getBoolean("java.net.preferIPv6Addresses");
if (!preferIpv6) {
return false;
}
try {
return address.isReachable(100);
} catch (IOException e) {
// ignore
}
return false;
}

/**
* 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
*
* 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;
}

public static String getLocalHost() {
InetAddress address = getLocalAddress();
return address == null ? LOCALHOST : address.getHostAddress();
Expand Down Expand Up @@ -186,34 +232,43 @@ private static InetAddress getLocalAddress0() {
InetAddress localAddress = null;
try {
localAddress = InetAddress.getLocalHost();
if (isValidAddress(localAddress)) {
if (localAddress instanceof Inet6Address) {
Inet6Address address = (Inet6Address) localAddress;
if (isValidV6Address(address)){
return normalizeV6Address(address);
}
} else if (isValidAddress(localAddress)) {
return localAddress;
}
} catch (Throwable e) {
logger.warn(e);
}
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces != null) {
while (interfaces.hasMoreElements()) {
try {
NetworkInterface network = interfaces.nextElement();
Enumeration<InetAddress> addresses = network.getInetAddresses();
if (addresses != null) {
while (addresses.hasMoreElements()) {
try {
InetAddress address = addresses.nextElement();
if (isValidAddress(address)) {
return address;
}
} catch (Throwable e) {
logger.warn(e);
if (null == interfaces) {
return localAddress;
}
while (interfaces.hasMoreElements()) {
try {
NetworkInterface network = interfaces.nextElement();
Enumeration<InetAddress> addresses = network.getInetAddresses();
while (addresses.hasMoreElements()) {
try {
InetAddress address = addresses.nextElement();
if (address instanceof Inet6Address) {
Inet6Address v6Address = (Inet6Address) address;
if (isValidV6Address(v6Address)){
return normalizeV6Address(v6Address);
}
} else if (isValidAddress(address)) {
return address;
}
} catch (Throwable e) {
logger.warn(e);
}
} catch (Throwable e) {
logger.warn(e);
}
} catch (Throwable e) {
logger.warn(e);
}
}
} catch (Throwable e) {
Expand Down
28 changes: 28 additions & 0 deletions dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -652,4 +652,32 @@ public void testUserNamePasswordContainsAt(){
assertEquals("1.0.0", url.getParameter("version"));
assertEquals("morgan", url.getParameter("application"));
}


@Test
public void testIpV6Address(){
// Test username or password contains "@"
URL url = URL.valueOf("ad@min111:haha@1234@2001:0db8:85a3:08d3:1319:8a2e:0370:7344:20880/context/path?version=1.0.0&application=morgan");
assertNull(url.getProtocol());
assertEquals("ad@min111", url.getUsername());
assertEquals("haha@1234", url.getPassword());
assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344", url.getHost());
assertEquals(20880, url.getPort());
assertEquals("context/path", url.getPath());
assertEquals(2, url.getParameters().size());
assertEquals("1.0.0", url.getParameter("version"));
assertEquals("morgan", url.getParameter("application"));
}

@Test
public void testIpV6AddressWithScopeId(){
URL url = URL.valueOf("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5/context/path?version=1.0.0&application=morgan");
assertNull(url.getProtocol());
assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getHost());
assertEquals(0, url.getPort());
assertEquals("context/path", url.getPath());
assertEquals(2, url.getParameters().size());
assertEquals("1.0.0", url.getParameter("version"));
assertEquals("morgan", url.getParameter("application"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package org.apache.dubbo.common.utils;

import org.apache.dubbo.common.utils.DubboAppender;
import org.apache.dubbo.common.utils.Log;
import org.apache.log4j.Category;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package org.apache.dubbo.common.utils;


import org.junit.Ignore;
import org.junit.Test;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;

Expand Down Expand Up @@ -180,4 +182,31 @@ public void testToURL() throws Exception {
String url = NetUtils.toURL("dubbo", "host", 1234, "foo");
assertThat(url, equalTo("dubbo://host:1234/foo"));
}

@Test
public void testIsValidV6Address() {
String saved = System.getProperty("java.net.preferIPv6Addresses", "false");
System.setProperty("java.net.preferIPv6Addresses", "true");
InetAddress address = NetUtils.getLocalAddress();
if (address instanceof Inet6Address) {
assertThat(NetUtils.isValidV6Address((Inet6Address) address), equalTo(true));
}
System.setProperty("java.net.preferIPv6Addresses", saved);
}

/**
* Mockito starts to support mocking final classes since 2.1.0
* see https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable
* But enable it will cause other UT to fail.
* Therefore currently disabling this UT.
*/
@Ignore
@Test
public void testNormalizeV6Address() {
Inet6Address address = mock(Inet6Address.class);
when(address.getHostAddress()).thenReturn("fe80:0:0:0:894:aeec:f37d:23e1%en0");
when(address.getScopeId()).thenReturn(5);
InetAddress normalized = NetUtils.normalizeV6Address(address);
assertThat(normalized.getHostAddress(), equalTo("fe80:0:0:0:894:aeec:f37d:23e1%5"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,23 @@

public class Consumer {

/**
* To get ipv6 address to work, add
* System.setProperty("java.net.preferIPv6Addresses", "true");
* before running your application.
*/
public static void main(String[] args) {
//Prevent to get IPV6 address,this way only work in debug mode
//But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
System.setProperty("java.net.preferIPv4Stack", "true");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
context.start();
DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy

while (true) {
try {
Thread.sleep(1000);
String hello = demoService.sayHello("world"); // call remote method
System.out.println(hello); // get result

} catch (Throwable throwable) {
throwable.printStackTrace();
}


}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@

public class Provider {

/**
* To get ipv6 address to work, add
* System.setProperty("java.net.preferIPv6Addresses", "true");
* before running your application.
*/
public static void main(String[] args) throws Exception {
//Prevent to get IPV6 address,this way only work in debug mode
//But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
System.setProperty("java.net.preferIPv4Stack", "true");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-provider.xml"});
context.start();

System.in.read(); // press any key to exit
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public void destroy() {
registry.unsubscribe(getConsumerUrl(), this);
}
} catch (Throwable t) {
logger.warn("unexpeced error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t);
logger.warn("unexpected error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t);
}
super.destroy(); // must be executed after unsubscribing
try {
Expand Down
Loading

0 comments on commit 413a2a0

Please sign in to comment.