无法从 java 连接到纯 IPv6 主机
Can't connect to IPv6-only host from java
我有一些仅支持 IPv6 的主机。我可以成功地对它执行 curl 请求
通过卷曲
$ curl -I my.ip.v6.only.host
HTTP/1.1 200 OK
但是当我尝试从 java 获取它时出现错误:
HttpGet httpget = new HttpGet("http://my.ip.v6.only.host");
CloseableHttpResponse response = httpclient.execute(httpget);
堆栈跟踪:
INFO: I/O exception (java.net.NoRouteToHostException) caught when processing request to {}->http://my.ip.v6.only.host: No route to host
Mar 17, 2015 7:42:23 PM org.apache.http.impl.execchain.RetryExec execute
INFO: Retrying request to {}->http://my.ip.v6.only.host
java.net.NoRouteToHostException: No route to host
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:579)
at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:72)
at org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:123)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:318)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:363)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:219)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:195)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:86)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)
at MainTest.main(MainTest.java:25)
问题发生在 java v1.7.0_65 和 v1.8.0_40,MacOS 10.10.2。在之前的 MacOS 10.9.5 版本上运行良好。
这是怎么回事? curl
可以访问主机而 java.
无法访问主机是怎么可能的
另外,我试过 -Djava.net.preferIPv6Addresses=true
和 -Djava.net.preferIPv4Stack=false
,但无济于事。
UPD 在 OpenJDK 中发现了一个相关的错误,JDK-8015415
UPD 2 当我尝试使用有线连接而不是 wifi 时,它帮助了我。奇怪。
有线连接对我也有帮助。
有
$ java -version
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
可能是AirDrop+Java配合的问题
简答 - 尝试:
$ sudo ifconfig awdl0 down
调查以下问题(感谢 Sergey Shinderuk):
我们在java中有这样的代码可以重现:
import java.net.Socket;
public class Test {
public static void main(String[] args) throws Exception {
new Socket("2a02:6b8::3", 80); // ya.ru
}
}
当我们使用 WiFi 时,出现异常:java.net.NoRouteToHostException: No route to host
虽然使用 telnet 一切正常:
$ telnet 2a02:6b8::3 80
Trying 2a02:6b8::3...
Connected to www.yandex.ru.
Escape character is '^]'.
^C
当我们关闭 wifi 并使用有线连接时 - 一切正常。但如果我们使用有线连接,但 wifi 已打开 - 此 java 代码将不起作用。这很奇怪。
我们需要比较 java 和 telnet 之间 connect(2)
的参数。
$ sudo dtrace -qn 'syscall::connect:entry { print(*(struct sockaddr_in6 *)copyin(arg1, arg2)) }' -c './telnet 2a02:6b8::3 80'
struct sockaddr_in6 {
__uint8_t sin6_len = 0x1c
sa_family_t sin6_family = 0x1e
in_port_t sin6_port = 0x5000
__uint32_t sin6_flowinfo = 0
struct in6_addr sin6_addr = {
union __u6_addr = {
__uint8_t [16] __u6_addr8 = [ 0x2a, 0x2, 0x6, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x3 ]
__uint16_t [8] __u6_addr16 = [ 0x22a, 0xb806, 0, 0, 0, 0, 0, 0x300 ]
__uint32_t [4] __u6_addr32 = [ 0xb806022a, 0, 0, 0x3000000 ]
}
}
__uint32_t sin6_scope_id = 0
}
您可以看到我们已经将 connect(2)
的第二个参数打印为结构 sockaddr_in6
。您还可以看到所有预期信息:AF_INET6
、端口 80 和 ipv6-address。
Make a note: we've launched ./telnet
, not telnet
- dtrace
can't
work with system binaries signed by Apple. So we should copy it.
java 相同:
$ sudo dtrace -qn 'syscall::connect:entry { print(*(struct sockaddr_in6 *)copyin(arg1, arg2)) }' -c '/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/bin/java Test'
[...]
struct sockaddr_in6 {
__uint8_t sin6_len = 0
sa_family_t sin6_family = 0x1e
in_port_t sin6_port = 0x5000
__uint32_t sin6_flowinfo = 0
struct in6_addr sin6_addr = {
union __u6_addr = {
__uint8_t [16] __u6_addr8 = [ 0x2a, 0x2, 0x6, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x3 ]
__uint16_t [8] __u6_addr16 = [ 0x22a, 0xb806, 0, 0, 0, 0, 0, 0x300 ]
__uint32_t [4] __u6_addr32 = [ 0xb806022a, 0, 0, 0x3000000 ]
}
}
__uint32_t sin6_scope_id = 0x8
}
如我们所见,主要区别在于 telnet 发送 sin6_len == 0
而 java - sin6_scope_id = 0x8
。主要问题恰恰在sin6_scope_id
。 telnet 和 curl 发送 scope_id == 0
,但 java - 0x8
。而当我们使用有线连接时,java发送scope_id == 0xb
。
明确地说,我们尝试使用 telnet 重现 scope_id
的问题。
使用 WiFi 做:
$ telnet 2a02:6b8::3%0 80
Trying 2a02:6b8::3...
Connected to www.yandex.ru.
$ telnet 2a02:6b8::3%8 80
Trying 2a02:6b8::3...
telnet: connect to address 2a02:6b8::3: No route to host
telnet: Unable to connect to remote host
$ telnet 2a02:6b8::3%b 80
Trying 2a02:6b8::3...
Connected to www.yandex.ru.
因此 telnet 可以连接 0xb
,但不能连接 0x8
。
java 这段代码的正确位置似乎是:
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/8fe85977d5a6/src/solaris/native/java/net/net_util_md.c#l105
我们已经看到 scope_id
填充了私有字段 java.net.NetworkInterface.defaultIndex 的值,其中包含一些默认接口的索引。
我们可以使用代码打印所有索引:
import java.lang.reflect.Field;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
List<NetworkInterface> netins = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface netin : netins) {
System.out.println(netin + " " + netin.getIndex());
}
Field f = NetworkInterface.class.getDeclaredField("defaultIndex");
f.setAccessible(true);
System.out.println("defaultIndex = " + f.get(NetworkInterface.class));
}
}
在 wifi 上:
$ java Netif
name:awdl0 (awdl0) 8
name:en0 (en0) 4
name:lo0 (lo0) 1
defaultIndex = 8
有线
$ java Netif
name:en4 (en4) 11
name:lo0 (lo0) 1
defaultIndex = 11
有线+wifi
$ java Netif
name:awdl0 (awdl0) 8
name:en4 (en4) 11
name:en0 (en0) 4
name:lo0 (lo0) 1
defaultIndex = 8
wifi连接时,defaultIndex == 8
,默认接口是awdl0.
所以我们只是
$ sudo ifconfig awdl0 down
和 java 代码有效。
还有:
WiFriedX 可以提供帮助。
什么是awd:What is AWDL (Apple Wireless Direct Link) and how does it work?
此补丁的作者是https://github.com/snaury。
解释:
你需要用otool打开libnet.dylib,找到_setDefaultScopeID符号:
otool -tv -p _setDefaultScopeID libnet.dylib
这里可以找到与0和条件跳转的对比:
000000000000b882 cmpb [=11=]x1e, 0x1(%r14)
000000000000b887 jne 0xb8aa
000000000000b889 cmpl [=11=]x0, 0x18(%r14)
000000000000b88e jne 0xb8aa
您需要用任何十六进制编辑器将条件跳转替换为无条件跳转:
000000000000b882 cmpb [=12=]x1e, 0x1(%r14)
000000000000b887 jne 0xb8aa
000000000000b889 cmpl [=12=]x0, 0x18(%r14)
000000000000b88e jmp 0xb8aa
JNE == 75 1a
JMP == eb 1a
或者使用这一行命令:
otool -tv -p _setDefaultScopeID libnet.dylib | awk '/cmpl.*$0x0/ {print }' | python -c 'exec """\nwith open("libnet.dylib", "r+b") as fd:\n fd.seek(int(raw_input(), 16) + 5)\n fd.write(chr(235))\n"""'
我有一些仅支持 IPv6 的主机。我可以成功地对它执行 curl 请求 通过卷曲
$ curl -I my.ip.v6.only.host
HTTP/1.1 200 OK
但是当我尝试从 java 获取它时出现错误:
HttpGet httpget = new HttpGet("http://my.ip.v6.only.host");
CloseableHttpResponse response = httpclient.execute(httpget);
堆栈跟踪:
INFO: I/O exception (java.net.NoRouteToHostException) caught when processing request to {}->http://my.ip.v6.only.host: No route to host
Mar 17, 2015 7:42:23 PM org.apache.http.impl.execchain.RetryExec execute
INFO: Retrying request to {}->http://my.ip.v6.only.host
java.net.NoRouteToHostException: No route to host
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:579)
at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:72)
at org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:123)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:318)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:363)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:219)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:195)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:86)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)
at MainTest.main(MainTest.java:25)
问题发生在 java v1.7.0_65 和 v1.8.0_40,MacOS 10.10.2。在之前的 MacOS 10.9.5 版本上运行良好。
这是怎么回事? curl
可以访问主机而 java.
另外,我试过 -Djava.net.preferIPv6Addresses=true
和 -Djava.net.preferIPv4Stack=false
,但无济于事。
UPD 在 OpenJDK 中发现了一个相关的错误,JDK-8015415
UPD 2 当我尝试使用有线连接而不是 wifi 时,它帮助了我。奇怪。
有线连接对我也有帮助。
有
$ java -version
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
可能是AirDrop+Java配合的问题
简答 - 尝试:
$ sudo ifconfig awdl0 down
调查以下问题(感谢 Sergey Shinderuk):
我们在java中有这样的代码可以重现:
import java.net.Socket;
public class Test {
public static void main(String[] args) throws Exception {
new Socket("2a02:6b8::3", 80); // ya.ru
}
}
当我们使用 WiFi 时,出现异常:java.net.NoRouteToHostException: No route to host
虽然使用 telnet 一切正常:
$ telnet 2a02:6b8::3 80
Trying 2a02:6b8::3...
Connected to www.yandex.ru.
Escape character is '^]'.
^C
当我们关闭 wifi 并使用有线连接时 - 一切正常。但如果我们使用有线连接,但 wifi 已打开 - 此 java 代码将不起作用。这很奇怪。
我们需要比较 java 和 telnet 之间 connect(2)
的参数。
$ sudo dtrace -qn 'syscall::connect:entry { print(*(struct sockaddr_in6 *)copyin(arg1, arg2)) }' -c './telnet 2a02:6b8::3 80'
struct sockaddr_in6 {
__uint8_t sin6_len = 0x1c
sa_family_t sin6_family = 0x1e
in_port_t sin6_port = 0x5000
__uint32_t sin6_flowinfo = 0
struct in6_addr sin6_addr = {
union __u6_addr = {
__uint8_t [16] __u6_addr8 = [ 0x2a, 0x2, 0x6, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x3 ]
__uint16_t [8] __u6_addr16 = [ 0x22a, 0xb806, 0, 0, 0, 0, 0, 0x300 ]
__uint32_t [4] __u6_addr32 = [ 0xb806022a, 0, 0, 0x3000000 ]
}
}
__uint32_t sin6_scope_id = 0
}
您可以看到我们已经将 connect(2)
的第二个参数打印为结构 sockaddr_in6
。您还可以看到所有预期信息:AF_INET6
、端口 80 和 ipv6-address。
Make a note: we've launched
./telnet
, nottelnet
-dtrace
can't work with system binaries signed by Apple. So we should copy it.
java 相同:
$ sudo dtrace -qn 'syscall::connect:entry { print(*(struct sockaddr_in6 *)copyin(arg1, arg2)) }' -c '/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/bin/java Test'
[...]
struct sockaddr_in6 {
__uint8_t sin6_len = 0
sa_family_t sin6_family = 0x1e
in_port_t sin6_port = 0x5000
__uint32_t sin6_flowinfo = 0
struct in6_addr sin6_addr = {
union __u6_addr = {
__uint8_t [16] __u6_addr8 = [ 0x2a, 0x2, 0x6, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x3 ]
__uint16_t [8] __u6_addr16 = [ 0x22a, 0xb806, 0, 0, 0, 0, 0, 0x300 ]
__uint32_t [4] __u6_addr32 = [ 0xb806022a, 0, 0, 0x3000000 ]
}
}
__uint32_t sin6_scope_id = 0x8
}
如我们所见,主要区别在于 telnet 发送 sin6_len == 0
而 java - sin6_scope_id = 0x8
。主要问题恰恰在sin6_scope_id
。 telnet 和 curl 发送 scope_id == 0
,但 java - 0x8
。而当我们使用有线连接时,java发送scope_id == 0xb
。
明确地说,我们尝试使用 telnet 重现 scope_id
的问题。
使用 WiFi 做:
$ telnet 2a02:6b8::3%0 80
Trying 2a02:6b8::3...
Connected to www.yandex.ru.
$ telnet 2a02:6b8::3%8 80
Trying 2a02:6b8::3...
telnet: connect to address 2a02:6b8::3: No route to host
telnet: Unable to connect to remote host
$ telnet 2a02:6b8::3%b 80
Trying 2a02:6b8::3...
Connected to www.yandex.ru.
因此 telnet 可以连接 0xb
,但不能连接 0x8
。
java 这段代码的正确位置似乎是: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/8fe85977d5a6/src/solaris/native/java/net/net_util_md.c#l105
我们已经看到 scope_id
填充了私有字段 java.net.NetworkInterface.defaultIndex 的值,其中包含一些默认接口的索引。
我们可以使用代码打印所有索引:
import java.lang.reflect.Field;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
List<NetworkInterface> netins = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface netin : netins) {
System.out.println(netin + " " + netin.getIndex());
}
Field f = NetworkInterface.class.getDeclaredField("defaultIndex");
f.setAccessible(true);
System.out.println("defaultIndex = " + f.get(NetworkInterface.class));
}
}
在 wifi 上:
$ java Netif
name:awdl0 (awdl0) 8
name:en0 (en0) 4
name:lo0 (lo0) 1
defaultIndex = 8
有线
$ java Netif
name:en4 (en4) 11
name:lo0 (lo0) 1
defaultIndex = 11
有线+wifi
$ java Netif
name:awdl0 (awdl0) 8
name:en4 (en4) 11
name:en0 (en0) 4
name:lo0 (lo0) 1
defaultIndex = 8
wifi连接时,defaultIndex == 8
,默认接口是awdl0.
所以我们只是
$ sudo ifconfig awdl0 down
和 java 代码有效。
还有:
WiFriedX 可以提供帮助。
什么是awd:What is AWDL (Apple Wireless Direct Link) and how does it work?
此补丁的作者是https://github.com/snaury。
解释:
你需要用otool打开libnet.dylib,找到_setDefaultScopeID符号:
otool -tv -p _setDefaultScopeID libnet.dylib
这里可以找到与0和条件跳转的对比:
000000000000b882 cmpb [=11=]x1e, 0x1(%r14)
000000000000b887 jne 0xb8aa
000000000000b889 cmpl [=11=]x0, 0x18(%r14)
000000000000b88e jne 0xb8aa
您需要用任何十六进制编辑器将条件跳转替换为无条件跳转:
000000000000b882 cmpb [=12=]x1e, 0x1(%r14)
000000000000b887 jne 0xb8aa
000000000000b889 cmpl [=12=]x0, 0x18(%r14)
000000000000b88e jmp 0xb8aa
JNE == 75 1a
JMP == eb 1a
或者使用这一行命令:
otool -tv -p _setDefaultScopeID libnet.dylib | awk '/cmpl.*$0x0/ {print }' | python -c 'exec """\nwith open("libnet.dylib", "r+b") as fd:\n fd.seek(int(raw_input(), 16) + 5)\n fd.write(chr(235))\n"""'