Llevaba ya algún tiempo con ganas de practicar con el comando ip y el otro día surgió la ocasión ideal al necesitar que dos aplicaciones (no preparadas para ello) recibieran un mismo flujo de información multicast en el mismo equipo.

El problema surgió cuando se instaló en un equipo una aplicación que leía el mismo flujo multicast que otra ya instalada previamente, solo la primera en arrancar recibía el flujo, independientemente de cual fuera. Este comportamiento es consecuencia de que ambas aplicaciones abren el socket de recepción del tráfico multicast en modo exclusivo (no es que lo hagan explícitamente, es que es el modo por defecto). Si ambas aplicaciones hubieran establecido la opción SO_REUSEADDR en el socket de escucha antes de hacer bind(), ambas podrían recibir el mismo flujo, pero no era este el caso.

La solución obvia es modificar las aplicaciones, así no presentarán este problema en futuros despliegues. Pero también hay otra solución más rápida y que puede utilizarse incluso con aplicaciones que no puedan modificarse por no disponer de su código fuente; hacer uso de los espacios de nombres (namespace) ofrecidos por el núcleo de Linux.

Un espacio de nombres (namespace), o más concretamente, un espacio de nombres de red (network namespace) es un entorno particular dentro de una misma instancia del sistema operativo que tiene su propio conjunto de interfaces de red y tablas de enrutamiento, separado del de otros espacios de nombres de la misma instancia del sistema operativo.

Por tanto, si creamos un espacio de nombres particular para una de las aplicaciones, mientras dejamos la otra en el espacio de nombres global del sistema operativo, ambas podrán abrir el socket de escritura sobre el mismo puerto sin cambiar su forma de hacerlo. Para ello se necesita crear un interfaz en el espacio de nombres adicional que reciba el flujo multicast al igual que lo hace el interfaz físico (o no) en el que escucha la otra aplicación en el espacio de nombres global.

La idea estaba clara, ahora quedaba ponerla en práctica con las piezas que pone a disposición el conjunto de herramientas iproute2. No describiré el par de cabezazos que pegué por ir a pecho descubierto sin antes haber leído con cierto detalle la documentación del comando ip y, sobre todo, el funcionamiento de los distintos tipos de interfaces virtuales que se pueden crear.

Una vez correctamente (no completamente) documentado, decidí aplicar la siguiente configuración con un interfaz de tipo bridge, que es una especie de switch (conmutador ya me parece demasiado aunque sea correcto), al que asociaría el interfaz donde se recibe el flujo multicast y uno de los extremos de un par de interfaces ligados entre sí a modo de tubería creados a partir de un dispositivo tipo veth. El otro extremo de esa tubería se coloca en el espacio de nombres particular para la otra aplicación y listo. A continuación una imagen que vale más que este párrafo:

Una vez soltado todo el rollo puedo pasar ya a la chicha que son los comandos a ejecutar para realizar ese despliegue.

En primer lugar se crea el espacio de nombres adicional:

#ip netns add ns0

Se crea la tubería que conectará los dos espacios de nombres:

#ip link add type veth

Esta orden crea automáticamente dos interfaces llamados veth0 y veth1 (en el caso de que sean los primeros). También se pueden crear dichos interfaces con los nombres que uno desee de la siguiente forma:

#ip link add vethA type veth peer name vethB

Se crea el puente y se conectan a él el interfaz que recibe el flujo y un extremo de la tubería:

#ip link add dev br0 type bridge
#ip link set eth0 master br0
#ip link set veth0 master br0

Se mueve el otro extremo de la tubería al espacio de nombres adicional y se le asigna una dirección IP y una puerta de enlace en la misma subred que el interfaz eth0:

#ip link set veth1 netns ns0

Una vez situado veth1 en el espacio de nombres ns0, todas las operaciones a realizar sobre él deben realizarse desde dicho espacio de nombres, de hecho, habrá ya desaparecido de la lista de interfaces que se puede consultar ejecutando ip link.

Para facilitar las cosas se puede lanzar un intérprete de comandos en el espacio de nombres y todas las órdenes se ejecutarán en dicho espacio de nombres, pero para que quede más claro aquí pondré las órdenes a ejecutar desde el espacio de nombres global:

#ip netns exec ns0 ip address add 192.168.1.11/24 dev veth1
#ip netns exec ns0 ip route add default vial 192.168.1.1 dev veth1
#ip netns exec ns0 ip link set veth1 up

Y listo, aunque puede que se me haya olvidado levantar los interfaces br0 y veth0, por lo que añado estas dos líneas que, en cualquier caso, no hacen daño:

#ip link set br0 up
#ip link set veth0 up

Ahora.

Algunas referencias:

http://man7.org/linux/man-pages/man7/socket.7.html

http://rg3.name/201504241907.html

http://blog.scottlowe.org/2013/09/04/introducing-linux-network-namespaces/

http://www.policyrouting.org/iproute2.doc.html

El diagrama lo hice en 5 minutos con https://www.draw.io/

 

Dejar una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Puedes utilizar estas etiquetas y atributos HTML:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.