代码之家  ›  专栏  ›  技术社区  ›  just-boris

无法从java连接到仅IPv6主机

  •  10
  • just-boris  · 技术社区  · 10 年前

    我有一些仅支持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)
    

    在MacOS 10.10.2的java v17.0_65和v18.0_40上出现了问题。在以前的MacOS 10.9.5版本上,它运行良好。

    发生什么事?如何通过 curl 并且无法从java访问。

    而且,我也试着四处玩耍 -Djava.net.preferIPv6Addresses=true -Djava.net.preferIPv4Stack=false 但这无济于事。

    更新 在OpenJDK中发现了一个相关的bug, JDK-8015415

    更新2 当我尝试用有线连接代替wifi时,这对我很有帮助。

    4 回复  |  直到 10 年前
        1
  •  16
  •   Community CDub    7 年前

    在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代码将不起作用。这很奇怪。

    我们需要比较 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
    }
    

    你可以看到,我们已经打印了 连接(2) 作为结构 sockaddr_in6 。此外,您还可以看到所有预期信息: AF_INET6 、端口80和ipv6地址。

    记下:我们已启动 ./telnet telnet - dtrace 不能 使用Apple签名的系统二进制文件。所以我们应该复制它。

    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 .

    为了清楚起见,我们试图用 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.
    

    所以telnet可以与 0xb ,但不能与 0x8个 .

    这段java代码的正确位置似乎是: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/8fe85977d5a6/src/solaris/native/java/net/net_util_md.c#l105

    我们已经看到了 作用域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
    

    有线+无线网络

    $ 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关闭
    

    并且java代码可以工作。

    也:

        2
  •  8
  •   Alexander Gryanko    7 年前

    此修补程序的作者是 https://github.com/snaury .

    说明:

    您需要使用otool打开libnet.dylib并找到_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"""'
    
        3
  •  0
  •   user3127286    9 年前

    有线连接对我也有帮助。

    具有 $ 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)

        4
  •  0
  •   Eric Lian    2 年前

    我昨天遇到了这个问题。我通过超越DNSResolver 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()
        );