Servicios Linux escuchando por el mismo puerto

rpc-diagram.png

Cuando leí sobre la nueva funcionalidad introducida en el núcleo de Linux en su versión 3.9 me acordé de la entrada HTTPS, SSH y OpenVPN en un mismo puerto, ¿magia? en SecurityByDefault aunque son cosas distintas.

Como decía en la versión del núcleo de Linux 3.9, se introdujo una nueva característica relacionada con los Sockets, la cual permite poner a más de un servicio o demonio escuchando por el mismo puerto. Esto se consigue a través de una nueva opción llamada SO_REUSEPORT. Los parches para esta nueva funcionalidad fueron implementados por Tom Herbert, un ingeniero de Google.

Algunos de los argumentos que Tom Herbert puso sobre la mesa para implementar esta característica fueron:

  • En la forma tradicional en al que un servicio procesa conexiones, es entrando en un bucle sin fin e ir creando hilos (threads) para procesar cada petición. Por lo visto Herbert tiene situaciones en las que tiene que lidiar con 40000 peticiones por segundo, y ese único punto de entrada se acaba convirtiendo en un cuello de botella.
  • De nuevo, con el método tradicional multihilo, la carga de trabajo entre los hilos que están esperando peticiones no está balanceada. Es decir, que hay varios hilos que reciben casi todo el trabajo, mientras que otros pasan la mayor parte de su tiempo esperando y como consecuencia de ello, no se aprovechan todos los procesadores del sistema de una forma optimizada.
Esta nueva opción se puede utilizar tanto con sockets TCP como UDP. Tenemos que tener en cuenta 2 detalles:
  1. El primer servicio que empiece a escuchar por el puerto X, debe establecer la opción SO_REUSEPORT. Si esto no ocurre, ningún otro servicio podrá escuchar por dicho puerto.
  2. Los servicios que compartan puerto, deben tener un User ID efectivo y éste debe ser el mismo que el primer servicio que abrió el puerto. Con esto se consigue evitar el secuestro de puertos. De otra forma, un usuario malo malísimo, podría poner a escuchar algún servicio malicioso por un puerto con un servicio legítimo.
Veamos un pequeño ejemplo de como usar esta nueva opción. Para ello necesitamos un sistema con un núcleo Linux 3.9 o mayor. En mi caso voy a usar la versión inestable de Ubuntu 13.10, que tiene la versión 3.11: nucleo_version.png

Vamos a usar el siguiente script escrito en Ruby:

La línea 10 es la clave. Ahí es donde decimos que queremos compartir el puerto. Todo lo que nuestro servicio va a hacer es repetir lo que el cliente le mande, con el prefijo “Version 1” en este caso. Por lo tanto si arrancamos 2 servicios y nos conectamos al puerto 5555 varias veces:

dos_servicios.png

Otra de las bondades de esta nueva opción es que podemos agregar nuevos servicios en cualquier momento. Digamos que hemos arreglado un error en nuestro script y queremos ponerlo a escuchar. Vamos a modificar la salida del script a: “Version 2”, más lo que nos mande el cliente:

v2.png

Como vemos, ahora tenemos corriendo dos servicios “Version 1” y un servicio “Version 2”. De esta forma podemos tener nuestro servicio corriendo sin coste en el tiempo de despliego (deployment), con lo cual nunca dejamos de dar servicio. Una vez que tengamos corriendo nuestro nueva versión, podemos ir cerrando las “Versiones 1” e ir dejando levantando “Versiones 2”.

Creo que es una característica que da mucho juego. Como nota final decir que aunque esto haya llegado a Linux recientemente, es una característica que los sistemas BSD desde hace años.

Fuentes http://freeprogrammersblog.vhex.net/post/linux-39-introdued-new-way-of-writing-socket-servers/2 https://lwn.net/Articles/542629/