just-boris
just-boris

Reputation: 9766

Can't connect to IPv6-only host from java

I have some IPv6-only hosts. I can successfully execute curl request to it by curl

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

But when I trying to get it from java I have an error:

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

Stack trace:

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 has been occured on java v1.7.0_65 and v1.8.0_40, MacOS 10.10.2. On previous version MacOS 10.9.5 it works well.

What's going on? How it is possible that host is reachable by curl and unreachable from java.

Also, I've tried to play around -Djava.net.preferIPv6Addresses=true and -Djava.net.preferIPv4Stack=false and it couldn't help.

UPD found a related bug in OpenJDK, JDK-8015415

UPD 2 when I tried to use wired connection instead of wifi, it helped me. Weird.

Upvotes: 10

Views: 7989

Answers (4)

Eric Lian
Eric Lian

Reputation: 21

I got this problem yesterday. I solved it by overriding DNSResolver of PoolingHttpClientConnectionManager.

    private static DnsResolver getDnsResolver() {
        return host -> Arrays.stream(InetAddress.getAllByName(host))
            .filter(it -> it instanceof Inet6Address)
            .toArray(InetAddress[]::new);
    }

////////

    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
        RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", SSLConnectionSocketFactory.getSocketFactory())
            .build(),
        getDnsResolver()
    );

Upvotes: 0

Alexander Gryanko
Alexander Gryanko

Reputation: 682

The author of this patch is https://github.com/snaury.

Explanation:

You need to open libnet.dylib with otool and find _setDefaultScopeID symbol:

otool -tv -p _setDefaultScopeID libnet.dylib

Here you can find comparison with 0 and conditional jump:

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

You need to replace conditional jump to unconditional jump with any hex editor:

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

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

Or use this one line command:

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"""'

Upvotes: 8

lanwen
lanwen

Reputation: 2298

It can be problem in AirDrop + Java cooperation.

Short answer - try:

$ sudo ifconfig awdl0 down

Investigation of the problem below (thanks for Sergey Shinderuk):

We have such code in java to reproduce:

import java.net.Socket;

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

And when we use WiFi, get exception: java.net.NoRouteToHostException: No route to host

While with telnet all ok:

$ telnet 2a02:6b8::3 80
Trying 2a02:6b8::3...
Connected to www.yandex.ru.
Escape character is '^]'.
^C

When we turn off wifi, and use wired connection - all ok. But if we used wired connection, but wifi is turned on - this java code won't work. Which is very strange.

We need to compare arguments for the connect(2) between java and 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
}

You can see that we have printed second argument of connect(2) as struct sockaddr_in6. Also you can see all expected info: AF_INET6, port 80, and 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.

Same for 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
}

As we can see, the main difference is that telnet sends sin6_len == 0 but java - sin6_scope_id = 0x8. The main problem in exactly sin6_scope_id. telnet and curl sends scope_id == 0, but java - 0x8. And when we use wired connection, java sends scope_id == 0xb.

To be clear, we try to reproduce problem with scope_id with telnet. Using WiFi do:

$ 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 can connect with 0xb, but can't with 0x8.

It seems that right place of this code for java is: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/8fe85977d5a6/src/solaris/native/java/net/net_util_md.c#l105

We've seen that scope_id filled with value of private field java.net.NetworkInterface.defaultIndex, which contains index of some default interface.

We can print all indexes with code:

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

On wifi:

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

On wired

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

On wired+wifi

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

When wifi connected, defaultIndex == 8, and default interface is awdl0.

So we just

$ sudo ifconfig awdl0 down

and java code works.

Also:

Upvotes: 16

user3127286
user3127286

Reputation: 111

Wired connection helps me too.

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

Upvotes: 0

Related Questions