Forum général.général Science-fiction : 2 applications sur le même port ?

Posté par  (site web personnel) .
Étiquettes : aucune
0
22
avr.
2011

Bonjour,

ce matin j'ai été ammené à faire un tunnel SSH via le port 80 de ma machine vers le port 80 d'une autre machine.

En soit, rien de palpitant. Sauf qu'une fois la solution temporaire validée, j'ai voulu mettre en place une solution définitive à base de dev spécifique. Je lance mon programme, et paf, "address already in use".

Effectivement, je viens de constater qu'il y avait un serveur http sur le port 80 (un ibm http server, si vous voulez tout savoir)

Du coup la grande question que je me pose, c'est comment mon navigateur web a pu passer par le port 80 de mon tunnel, alors que théoriquement le tunnel lui-même n'aurait jamais pu s'ouvrir car le port 80 était déjà occupé !

Ce qui est fort, c'est que si je coupe mon tunnel, le serveur http reprend tout de suite la main. Si je relance putty, c'est le tunnel qui reprend la main. Si par contre je lance mon putty en premier avec le http server éteint, alors le serveur http plante au démarrage.

Donc voilà, si quelqu'un peut m'éclairer sur ce comportement digne de la science-fiction, pour moi pauvre développeur ayant séché beaucoup de cours de réseaux, et bien je suis preneur !

Merci à vous :)

  • # Adresse d'écoute

    Posté par  (site web personnel) . Évalué à 2.

    Je pense que les deux écoutes (PuTTY et le IBM http server) n'utilisent pas la même adresse d'écoute. PuTTY se met normalement sur 127.0.0.1. Ce ne doit pas être le cas de ton programme et du IBM http server.

    • [^] # Re: Adresse d'écoute

      Posté par  (site web personnel) . Évalué à 1.

      Hum, c'est pertinent.

      J'ai changé mon code :
      - server = new ServerSocket(Integer.parseInt(args[1]),5,InetAddress.getByName("localhost"));
      au lieu de
      - server = new ServerSocket(Integer.parseInt(args[1]));

      Et quand j'affiche server.getInetAddress().toString() je suis bien désormais sur la boucle locale quand je le lance depuis mon poste. Mais ca ne marche toujours pas quand je démarre le programme sur l'autre machine (address already in use)

      • [^] # Re: Adresse d'écoute

        Posté par  (site web personnel) . Évalué à 1.

        netstat -nta pourra te donner des informations précieuses.

        • [^] # Re: Adresse d'écoute

          Posté par  (site web personnel) . Évalué à 1.

          Bon, petite précision, mon problème a lieu sur une machine windows !

          Mais le netstat me remonte juste :
          TCP 0.0.0.0:80 0.0.0.0:0 LISTENING

          0.0.0.0 ca veut pas dire "toutes les interfaces" (y compris 127.0.0.1, donc) ?

          • [^] # Re: Adresse d'écoute

            Posté par  (site web personnel) . Évalué à 2.

            N'importe quelle adresse

            Système - Réseau - Sécurité Open Source - Ouvert à de nouvelles opportunités

            • [^] # Re: Adresse d'écoute

              Posté par  (site web personnel) . Évalué à 1.

              J'en reviens donc à ma question de départ : comment putty fait-il pour reprendre le dessus sur le http server alors que celui-ci est déjà démarré, et lui rendre la main une fois terminé ?!!

              • [^] # Re: Adresse d'écoute

                Posté par  (site web personnel) . Évalué à 1.

                Alors, j'ai découvert le -o de netstat windows qui m'indique un peu plus d'info (le pid notamment)

                du coup, j'ai avant de lancer putty :

                TCP 0.0.0.0:80 0.0.0.0:0 LISTENING 384

                Image Name PID Session Name Session# Mem Usage
                ========================= ====== ================ ======== ============
                httpd.exe 384 RDP-Tcp#34 0 5,700 K

                Après avoir lancé putty :

                TCP 0.0.0.0:80 0.0.0.0:0 LISTENING 384
                TCP 127.0.0.1:80 0.0.0.0:0 LISTENING 6584

                Image Name PID Session Name Session# Mem Usage
                ========================= ====== ================ ======== ============
                putty.exe 6584 RDP-Tcp#34 0 4,316 K

                Mon programme java n'arrive pas à binder 127.0.0.1:80 si un programme est déjà bindé sur 0.0.0.0:80, alors pourquoi putty le peut ?

                • [^] # Re: Adresse d'écoute

                  Posté par  (site web personnel) . Évalué à 1.

                  Possible que comme une table de routage, le cheminement s'effectue du plus précis en premier. ( vers le + génerique )
                  Donc si une appli écoute sur une adresse définie port 80 par exemple, elle aura priorité à une autre ( 0.0.0.0: 80 ).

                  Système - Réseau - Sécurité Open Source - Ouvert à de nouvelles opportunités

                  • [^] # Re: Adresse d'écoute

                    Posté par  (site web personnel) . Évalué à 1.

                    Mais comment je peux faire pour coder un programme qui arrive à se lancer sur 127.0.0.1 quand le meme port est déjà bindé sur 0.0.0.0 par une autre application ? L'API java me jette comme une crotte ! Pour elle (et pour moi aussi d'ailleurs), 0.0.0.0 inclut 127.0.0.1

  • # SO_REUSEADDR et SO_REUSEPORT

    Posté par  . Évalué à 4.

    • [^] # Re: SO_REUSEADDR et SO_REUSEPORT

      Posté par  . Évalué à 5.

      Un peu plus de détails, avec un petit script pour expérimenter :

      """
      import socket
      import sys
      import optparse

      parser = optparse.OptionParser()
      parser.add_option('-P', dest='protocol', default='tcp')
      parser.add_option('-p', dest='port', type='int', default=4242)
      parser.add_option('-r', dest='reuse', action='store_true', default=False)
      options, args = parser.parse_args()

      if options.protocol == 'udp':
      s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      else:
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

      if options.reuse:
      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

      try:
      s.bind(('', options.port))
      if options.protocol == 'tcp':
      s.listen(3)
      raw_input()
      finally:
      s.close()
      """

      En gros :
      - sans SO_REUSEADDR, tu te prends un EADDRINUSE en TCP/UDP si tu te bindes au même couple (interface, port)
      - avec SO_REUSEADDR sur chaque socket, en UDP tu peux te binder plusieurs fois au même (interface, port), mais le comportement n'est déterministe que si tu reçois du broadcast/multicast (tous les socket srecoivent les données), en unicast il n'y a aucune garantie, généralement le premier socket va tout recevoir, et les autres rien
      - en TCP, avec SO_REUSEADDR sur chaque socket, tu peux te binder plusieurs fois seulement si l'autre socket n'est pas en LISTEN (puisque plusieurs bind sur le même port alors qu'une communication unicast est établie n'a pas de sens)
      Windows semble autoriser un bind multiple en TCP même si l'autre socket est en LISTEN, enfin je n'ai pas de Windows sous la main, et si c'est le cas ce n'est absolument pas normal/standard.
      Enfin, l'intérêt principal de SO_REUSEADDR c'est de pourvoir réutiliser une adresse en TIME-WAIT.

      J'ai laissé de côté SO_REUSEPORT parce qu'il n'est plus utilisé sous Linux:

      /usr/include/asm-generic/socket.h:#define SO_REUSEADDR 2
      /usr/include/asm-generic/socket.h:/* To add :#define SO_REUSEPORT 15 */

      • [^] # Re: SO_REUSEADDR et SO_REUSEPORT

        Posté par  (site web personnel) . Évalué à 1.

        bingo

        J'ai positionné le REUSEADDRESS via la methode java setReuseAddress(true) et désormais le programme fait bien comme putty, à savoir qu'il se lance sans planter, et qu'il répond à la place du HTTPServer sur le port 80, et ce dernier reprend immédiatement la main quand je tue mon programme.

        Ce qui est bizarre, c'est que la doc indique bien que REUSEADDRESS n'est censé marcher que si l'appli qui écoute déjà est en TIMEWAIT, or ici mon http server est en LISTENING sur 0.0.0.0

        Est-ce normal ? N'est-ce pas là une faille de sécurité ?

        • [^] # Re: SO_REUSEADDR et SO_REUSEPORT

          Posté par  . Évalué à 1.

          Est-ce normal ?

          Non. Linux ne l'autorise pas.

          N'est-ce pas là une faille de sécurité ?

          Il faut déjà que ton serveur ait activé SO_REUSEADDR de son côté (bon, c'est courant).
          Mais c'est aussi pour cela qu'on a inventé les ports réservés (<1024).
          Là je suppose que ton programme tourne en administrateur, j'espère que tu es sûr de ton code...

  • # non

    Posté par  (site web personnel) . Évalué à 2.

    A priori, avoir deux applications qui écoutent sur le même (interface:port) en même temps n'a pas de sens. Ce qui est possible c'est des les faire écouter sur des (interface:port) différent et d'avoir un espèce de proxy qui redirige vers l'un ou l'autre selon des critères arbitraires. Par exemple : http://sam.zoy.org/blog/20070423-redirect.c

    pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

    • [^] # Re: non

      Posté par  (site web personnel) . Évalué à 1.

      Elles n'écoutent pas en "même temps" puisque seule la dernière lancée recoit le flux. Mais elle courcircuite la première application durant toute sa durée de vie.

      Pour reproduire le comportement, simplement faire un serveur qui écoute sur 0.0.0.0:80, et un autre sur 127.0.0.1:80 avec le flag REUSEADDRESS (sinon paf, address already in use)

      Le mystère du pourquoi ca marche reste entier cela dit...

  • # Et pour la démonstration

    Posté par  (site web personnel) . Évalué à 1.

    Voilà, un petit exemple java bien dégeu qui illustre le truc du flag REUSEADDRESS

    Il suffit de lancer deux fois le programme (le deuxieme devrait s'appeler server TWO automatiquement). Si on attaque 127.0.0.1 avec un browser c'est bien server TWO qui répond, mais si on le tue, alors c'est server ONE qui répond.

    Si quelqu'un peut m'expliquer pourquoi on arrive à ce comportement, je suis preneur.

    import java.io.BufferedWriter;
    import java.io.OutputStreamWriter;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.SocketException;
    import java.util.Date;

    public class Foo{

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception{
        String serverName = "Server number ONE";
    
        if (args.length != 1){
            System.out.println("Usage: MicroServer.jar <port>");
            System.exit(1);
        }
    
        int port = Integer.parseInt(args[0]);
    
        ServerSocket server = new ServerSocket();
    
    
        try{
            server.bind(new InetSocketAddress("0.0.0.0",port));
        }
        catch (SocketException se){
            server = new ServerSocket();
            server.setReuseAddress(true);
            serverName = "Server number TWO";
            server.bind(new InetSocketAddress("localhost",port));
        }
    
        System.out.println(serverName + " is now listening on port " + port);
    
        while (true){
            Socket s = server.accept();
            System.out.println("[" + new Date().toString() + "] new connection from " + s.getInetAddress().toString());
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            writer.write("<html><title>Hello</title><body>Hello, I am " + serverName + "</body></html>");
            writer.close();
            s.close();            
        }
    
    }
    

    }

    • [^] # Re: Et pour la démonstration

      Posté par  (site web personnel) . Évalué à 2.

      je viens de tester sur une machine linux, et la deuxième tentative de connexion plante bien comme il faut. Le problème serait donc inhérent à windows (testé sur windows Seven et XP)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.