2015-03-17 13 views
8

Ich habe einige IPv6-only-Hosts. Ich kann erfolgreich curl Anfrage an sie vonIch kann keine Verbindung zu IPv6-only-Host von Java

$ curl -I my.ip.v6.only.host 
HTTP/1.1 200 OK 

curl auszuführen Aber wenn ich es von Java zu erhalten versuchen, habe ich einen Fehler:

HttpGet httpget = new HttpGet("http://my.ip.v6.only.host"); 
CloseableHttpResponse response = httpclient.execute(httpget);  

Stapelüberwachung:

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) 

Problem ist auf Java v1.7.0_65 und v1.8.0_40, MacOS 10.10.2 aufgetreten. Bei der Vorgängerversion MacOS 10.9.5 funktioniert es gut.

Was ist los? Wie es möglich ist, dass der Host von curl erreichbar und von Java nicht erreichbar ist.

Auch habe ich versucht, um -Djava.net.preferIPv6Addresses=true und -Djava.net.preferIPv4Stack=false zu spielen und es konnte nicht helfen.

UPD fand einen ähnlichen Fehler in OpenJDK, JDK-8015415

UPD 2, wenn ich anstelle von Wi-Fi Kabelverbindung zu nutzen versucht, es mir geholfen. Seltsam.

+0

Welche Version von Apache Http Client verwenden Sie? – Martijn

+0

org.apache.httpcomponents: httpclient: 4.3.5 –

+0

Erprobte normale Java NIO-Anfragen noch? Ich kann nicht scheinen, dieses Problem auf einer Linux-Box atm neu zu erstellen. – Martijn

Antwort

14

kann es sein Problem in AirDrop + Java Zusammenarbeit.

Kurze Antwort - versuchen:

$ sudo ifconfig awdl0 down 

Untersuchung des Problems unten (Danke für Sergey Shinderuk):

Wir in Java solchen Code haben zu reproduzieren:

import java.net.Socket; 

public class Test { 
    public static void main(String[] args) throws Exception { 
     new Socket("2a02:6b8::3", 80); // ya.ru 
    } 
} 

Und wenn Wir verwenden WiFi, erhalten Ausnahme: java.net.NoRouteToHostException: No route to host

Whil e mit telnet alles ok:

Wenn wir WLAN ausschalten, und Kabelverbindung verwenden - alles in Ordnung. Aber wenn wir eine kabelgebundene Verbindung verwenden, aber WLAN eingeschaltet ist - dieser Java-Code wird nicht funktionieren. Das ist sehr seltsam.

Wir müssen Argumente für die connect(2) zwischen Java und Telnet vergleichen.

$ 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 
} 

Sie können sehen, dass wir zweites Argument von connect(2) als struct gedruckt haben. Sie können auch alle erwarteten Informationen sehen: AF_INET6, Port 80 und IPv6-Adresse.

Make a note: we've launched ./telnet , not telnet - dtrace can't work with system binaries signed by Apple. So we should copy it.

Das Gleiche gilt für 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 
} 

Wie wir sehen können, ist der Hauptunterschied, dass Telnet sendet sin6_len == 0 aber java - sin6_scope_id = 0x8. Das Hauptproblem in genau sin6_scope_id. Telnet und Curl sendet scope_id == 0, aber Java - 0x8. Und wenn wir Kabelverbindung verwenden, sendet Java scope_id == 0xb.

Um klar zu sein, versuchen wir, Problem mit scope_id mit Telnet zu reproduzieren. Mit WiFi tun:

$ 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. 

So telnet mit 0xb verbinden kann, kann aber nicht mit 0x8.

Es scheint, dass die richtige Ort, diesen Code für Java ist: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/8fe85977d5a6/src/solaris/native/java/net/net_util_md.c#l105

Wir haben gesehen, dass scope_id mit dem Wert des privaten Feld java.net.NetworkInterface.defaultIndex gefüllt, der Index von einiger Standard-Schnittstelle enthält.

Wir können alle Indizes mit Code drucken:

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)); 
    } 
} 

Auf wifi:

$ java Netif 
name:awdl0 (awdl0) 8 
name:en0 (en0) 4 
name:lo0 (lo0) 1 
defaultIndex = 8 

Auf verdrahteten

$ java Netif 
name:en4 (en4) 11 
name:lo0 (lo0) 1 
defaultIndex = 11 

Auf verdrahtet + wifi

$ java Netif 
name:awdl0 (awdl0) 8 
name:en4 (en4) 11 
name:en0 (en0) 4 
name:lo0 (lo0) 1 
defaultIndex = 8 

Wenn WiFi verbunden ist, defaultIndex == 8, und die Standardschnittstelle ist awdl0.

Also nur wir

$ sudo ifconfig awdl0 down 

und Java-Code funktioniert.

auch:

+0

Verknüpft mit https://youtrack.jetbrains.com/issue/WI-26878#comment=27-1069340 – lanwen

+0

Fantastische Antwort, danke! Leider gibt es Fälle, in denen 'awdl0' nicht ausreicht. Auf meinem MacBook zeigt mein 'defaultIndex' auf' en5', wobei die vollständige Liste lautet: en5, utun2, utun1, utun0, awdl0, en0, lo0; wo en0 ist derjenige, der verwendet werden muss. Ziemlich schmerzhaft. Interessanterweise weiß Java bereits, wie man es richtig macht, wenn Sie 'SocketChannel.open (inet6SockAddress) .socket()' verwenden, erhalten Sie einen funktionierenden Socket. Natürlich gibt es _tons_ von Java-Code, der die Standard-Socket-API verwendet, also scheint dies ein großes Problem für IPv6 auf Mac zu sein. – Lachlan

+0

oneliner von Alexander Gryanko funktioniert! – lanwen

0

Wired Verbindung hilft mir auch.

Mit $ 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)

8

Der Autor dieses Patch ist https://github.com/snaury.

Erklärung:

Sie müssen libnet öffnen.dylib mit otool und finden _setDefaultScopeID Symbol:

otool -tv -p _setDefaultScopeID libnet.dylib 

Hier können Sie Vergleich mit 0 und bedingtem Sprung finden:

000000000000b882 cmpb $0x1e, 0x1(%r14) 
000000000000b887 jne 0xb8aa 
000000000000b889 cmpl $0x0, 0x18(%r14) 
000000000000b88e jne 0xb8aa 

Sie müssen bedingten Sprung unbedingten Sprung mit einem beliebigen Hex-Editor ersetzen:

000000000000b882 cmpb $0x1e, 0x1(%r14) 
000000000000b887 jne 0xb8aa 
000000000000b889 cmpl $0x0, 0x18(%r14) 
000000000000b88e jmp 0xb8aa 

JNE == 75 1a 
JMP == eb 1a 

Oder verwenden diese eine Zeile Befehl:

otool -tv -p _setDefaultScopeID libnet.dylib | awk '/cmpl.*\$0x0/ {print $1}' | 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"""' 
+1

Vielen Dank für One-Liner! Es sollte erwähnt werden, dass libnet.dylib in jre ähnlich wie '/ Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Inhalt/Home/jre/lib/ibnet.dylib' sein sollte. – lanwen

+0

Vielen Dank, funktioniert mit 1.8.0_162 auch! – bashnesnos