2015-03-17 2 views
8

У меня есть только хосты с IPv6. Я могу успешно выполнить локон запрос на него от завиткаНе удается подключиться к IPv6-узлу из java

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

Stack след:

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, когда я пытался использовать проводное соединение вместо Wi-Fi, он помог мне. Weird.

+0

Какую версию Apache Http Client вы используете? – Martijn

+0

org.apache.httpcomponents: httpclient: 4.3.5 –

+0

Пробовал обычные запросы Java NIO еще? Кажется, я не могу воссоздать эту проблему на Linux box atm. – Martijn

ответ

14

Это может быть проблемой в сотрудничестве десантный + Java.

Короткий ответ - попробуйте:

$ sudo ifconfig awdl0 down 

Исследование ниже проблемы (спасибо за Сергея 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 2a02:6b8::3 80 
Trying 2a02:6b8::3... 
Connected to www.yandex.ru. 
Escape character is '^]'. 
^C 

Когда мы выключаем Wi-Fi, а также использовать проводное соединение - все ок. Но если мы использовали проводное соединение, но Wi-Fi включен - этот код Java не будет работать. Это очень странно.

Нам нужно сравнить аргументы для connect(2) между java и telnet.

$ 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) в качестве структуры . Также вы можете увидеть всю ожидаемую информацию: AF_INET6, порт 80 и адрес ipv6.

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 
} 

Как мы видим, основное отличие состоит в том, что телнет посылает sin6_len == 0, но Java - sin6_scope_id = 0x8. Основная проблема в точности sin6_scope_id. telnet и curl отправляет scope_id == 0, но java - 0x8. И когда мы используем проводное соединение, java отправляет scope_id == 0xb.

Чтобы быть ясным, мы пытаемся воспроизвести проблему с помощью scope_id с помощью telnet. Использование 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. 

Так телнет может соединиться с 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)); 
    } 
} 

На Wi-Fi:

$ 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 

На проводных + Wi-Fi

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

Когда Wi-Fi подключен, defaultIndex == 8, а интерфейс по умолчанию - awdl0.

Так что мы просто

$ sudo ifconfig awdl0 down 

и Java-код работает.

также:

+0

Ссылка на https://youtrack.jetbrains.com/issue/WI-26878#comment=27-1069340 – lanwen

+0

Фантастический ответ, спасибо! К сожалению, случаи, когда «awdl0» недостаточно, недостаточно. На моем MacBook мой 'defaultIndex' указывает на' en5', полный список: en5, utun2, utun1, utun0, awdl0, en0, lo0; где en0 - тот, который должен использоваться. Довольно болезненно. Интересно, что Java уже знает, как это сделать правильно, если вы используете 'SocketChannel.open (inet6SockAddress) .socket()', вы получите рабочий сокет. Конечно, есть _tons_ кода Java, который использует стандартный API 'Socket', поэтому это кажется огромной проблемой для IPv6 на Mac. – Lachlan

+0

oneliner от Александра Грянко работает! – lanwen

0

Проводное соединение также помогает мне.

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

Автор этого патча https://github.com/snaury.

Объяснение:

Вам нужно открыть ЛИБНЕТ.dylib с otool и найти _setDefaultScopeID символ:

otool -tv -p _setDefaultScopeID libnet.dylib 

Здесь вы можете найти сравнение с 0 и условным переходом:

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

Вам необходимо заменить условный переход на безусловный переход с любым шестнадцатеричным редактором:

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

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

Или используйте одну команду:

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

Большое спасибо за один лайнер! Следует упомянуть, что libnet.dylib должен быть в jre-пути, подобном '/ Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/libnet.dylib' – lanwen

+0

Большое спасибо, работает с 1.8.0_162 также! – bashnesnos

Смежные вопросы