9
votes

Java Socket ne parvient pas à se connecter à "0.0.0.0" avec NoRouteToHostException au lieu de Connection Refused

Problème

Lors de l'ouverture d'un socket sur IP: 0.0.0.0 et Port: 37845 (juste un port fermé aléatoire) avec la classe de socket de java, le socket se connecte échoue avec une java.net.NoRouteToHostException sur Machine 1

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("0.0.0.0", 37845))

J'utilise ce testcode: p >

[qa@localhost ~]$ cat /etc/centos-release
CentOS Linux release 7.6.1810 (Core)
[qa@localhost ~]$ java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)
[qa@localhost ~]$ uname -a
Linux localhost.localdomain 3.10.0-957.1.3.el7.x86_64 #1 SMP Thu Nov 29 14:49:43 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

Attendu

Ce que j'attends en fait est une java.net.ConnectException: Connexion refusée (Connexion refusée) , qui c'est aussi ce que j'obtiens avec une autre machine Cent OS, appelons-la Machine2

[qa@jenkins-staging ~]$ cat /etc/centos-release
CentOS Linux release 7.6.1810 (Core)
[qa@jenkins-staging ~]$ java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)
[qa@jenkins-staging ~]$ uname -a
Linux jenkins-staging.fancydomain 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

Setup

Machine1:

Exception in thread "main" java.net.ConnectException: Connection refused (Connection refused)
        at java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:204)
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
        at java.net.Socket.connect(Socket.java:589)
        at Test.main(Test.java:26)

Machine2:

import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

public class Test {

 public static void main(String[] args) throws Exception {

  Socket socket;

  // create a socket with a timeout
  SocketAddress socketAddress = new InetSocketAddress("0.0.0.0", 37845);

  // create a socket
  socket = new Socket();

  // this method will block no more than timeout ms.
  int timeoutInMs = 10 * 1000; // 10 seconds
  socket.connect(socketAddress, timeoutInMs);
  System.err.println("SUCCESS");
 }
}

Il semble donc la seule différence est la version du noyau.

Autres choses que j'ai essayées:

  • J'ai essayé le "même" code avec python, là j'obtiens toujours un ConnectionRefused (sur Machine1 + Machine2 )

    Exception in thread "main" java.net.NoRouteToHostException: No route to host (Host unreachable)
        at java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:204)
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
        at java.net.Socket.connect(Socket.java:589)
        at Test.main(Test.java:26)
    
  • Ping 0.0.0.0 sur Machine1 fonctionne également et résout en 127.0.0.1
  • Le remplacement de 0.0.0.0 par 127.0.0.1 dans le code source résout le problème, et ConnectionRefused (attendu) est déclenché au lieu de NoRouteToHostException
  • Désactivation de Firewalld, désactivation de SELinux, etc.

Questions

  1. S'agit-il d'un bogue Java? Si tel est le cas, pourquoi fonctionne-t-il sur Machine2 même si ils utilisent le même jdk et la même version java?
  2. S'agit-il d'un bogue du noyau Linux? Si oui, pourquoi fonctionne-t-il avec Python lorsque j'ouvre un socket à 0.0.0.0 mais pas avec java? Je supposerais le sous-jacent les appels système sont les mêmes.

Clarification

Dans l'exemple ci-dessus, j'ai utilisé un port qui est fermé, juste à des fins de démonstration. La même chose s'applique s'il y a un port d'écoute réel sur la machine, alors ce sera ConnectionRefused vs SUCCESS


2 commentaires

Je ne sais pas pourquoi vous testez ça. C'est invalide de toute façon.


@ user207421 à l'origine à cause de ceci: github.com/jenkinsci/docker-plugin/pull/720 , mais pour le moment, cela me dérange simplement que les deux machines se comportent différemment et je ne peux pas dire pourquoi.


3 Réponses :


7
votes

0.0.0.0 est une adresse spéciale , qui fait partie de la plage spéciale 0.0.0.0/8 qui signifie "réseau actuel" ou "non spécifié". Vous ne pouvez pas vous y connecter car il n'est pas défini comme destination.

C'est pourquoi vous obtenez une NoRouteToHostException - l'adresse n'est tout simplement pas routable. Vous obtiendrez un échec similaire si vous essayez d'exécuter ping 0.0.0.0 ou une commande similaire.

ConnectionRefused se produit lorsque la machine distante refuse réellement la connexion, ce qui est généralement un signe que la machine distante n'a pas de socket d'écoute ou se trouve derrière un pare-feu.


5 commentaires

unix.stackexchange.com/a/419881 Cette réponse explique qu'en pratique vous pouvez la spécifier comme destination, mais évidemment comme OP a constaté que cela ne fonctionne pas toujours comme prévu.


Voici la documentation des erreurs de socket Java ce qui explique aussi la différence entre eux


@MarkReedZ Je n'ai jamais rien vu de tel dans la pratique, et malheureusement la réponse que vous avez citée ne fournit aucun exemple de ce qui se passe. AFAIK, 0.0.0.0 ne peut être utilisé que comme adresse source lors de la liaison d'un socket. Cela n'a absolument aucun sens en tant que destination. Si cela fonctionne sur une plate-forme, je le considérerais comme un bogue.


En fait, ce n'est pas vrai, veuillez consulter la section "Autres choses que j'ai essayées". Je mentionne que le ping 0.0.0.0 fonctionne. [qa @ jenkins-staging ~] $ ping 0.0.0.0 PING 0.0.0.0 (127.0.0.1) 56 (84) octets de données. 64 octets à partir de 127.0.0.1: icmp_seq = 1 ttl = 64 temps = 0,255 ms 64 octets à partir de 127.0.0.1: icmp_seq = 2 ttl = 64 temps = 0,113 ms ^ C --- 0.0.0.0 statistiques de ping --- 4 paquets transmis , 4 reçus, 0% de perte de paquets, temps 3000ms rtt min / moy / max / mdev = 0.113 / 0.149 / 0.255 / 0.062 ms


@Malt J'ai vu cela en pratique car il est utilisé dans le plugin docker pour jenkins, c'est ainsi que je suis tombé sur ce bug / comportement en premier lieu. Le plugin ne fonctionnait pas pour mes jenkins bien qu'un autre jenkins fonctionnait avec la même configuration. J'ai fait du débogage et c'est ainsi que j'ai découvert cela en premier lieu. Vous pouvez également essayer de vous connecter à 0.0.0.0 dans Chrome sur Ubuntu, bien que cela ne fonctionne pas dans Windows 10 Chrome.



2
votes

Je voudrais certainement installer Wireshark sur les deux machines et comparer tous les scénarios.

Plus précisément:

https://superuser.com/questions/720851/connection-refused -vs-no-route-to-host

"Connexion refusée" signifie que la machine cible a activement rejeté la connexion ... l'une des raisons suivantes est probablement la raison:

  • Rien n’écoute sur le port
  • Le pare-feu bloque la connexion avec REJECT

Le message ICMP, "pas de route vers l'hôte", signifie que ARP ne peut pas trouver le Adresse de couche 2 pour l'hôte de destination. Habituellement, cela signifie que que l'hôte avec cette adresse IP n'est pas en ligne ou ne répond pas.

Bien sûr, cela soulève la question de savoir pourquoi Python se comporte d'une manière et Java d'une manière différente ... sur la même machine.

Encore une fois, je vous encourage à regarder Wireshark. En particulier, regardez 1) la négociation TCP à trois voies, et 2) l'appel ARP qui le précède.


PS: Comme le malt dit ci-dessus:

0.0.0.0 ... l'adresse n'est tout simplement pas routable.

Sous Windows, vous pouvez obtenir WSAEADDRNOTAVAIL -L'adresse distante n'est pas une adresse valide

Ce qui soulève la question de savoir pourquoi vous obtenez "ConnectionRefused".

Encore une fois, je suis vraiment curieux de savoir ce que Wireshark vous montre.


2 commentaires

Pour une destination non routable, WireShark ne doit afficher aucun paquet. La pile TCP / IP du système d'exploitation doit renvoyer une erreur avant d'envoyer quoi que ce soit.


Merci pour votre réponse. En ce qui concerne le traçage du trafic, j'ai fait un tcpdump pour les requêtes arp mais même pas 1 requête arp n'est envoyée / reçue. En ce qui concerne le traçage de la poignée de main TCP, comme Malt l'a mentionné, cela n'a pas de sens car avec NoRouteToHostException il n'y aura même pas de paquet TCP SYN car il échoue avant cela.



0
votes

J'ai intégré le code que vous avez fourni et l'ai exécuté sur le système Windows 10, j'obtiens l'exception correcte

Exception in thread "main" java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:204)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at Main.main(Main.java:8)

Process finished with exit code 1

Oracle JDK 8.0.202

import java.net.InetSocketAddress;
import java.net.Socket;

public class Main {

    public static void main(String[] args) throws Exception {

        new Socket().connect(new InetSocketAddress("0.0.0.0", 37845), 10_0000);
    }
}

1 commentaires

C'est intéressant