La programación Multicast... o cómo escribir sus propias aplicaciones multicast.
Se necesitan diversas extensiones al API de programación para poder
soportar multicast. Todas ellas se manejan a través de dos llamadas al
sistema: setsockopt()
(usada para pasar información al
kernel) y getsockopt()
(para obtener información referente al
comportamiento multicast). Esto no significa que se añadan
dos nuevas llamadas al sistema para soportar multicast. El par
setsockopt()
/ getsockopt()
ha estado allí durante
años. O por lo menos desde 4.2 BSD. Lo que se añaden son un nuevo
conjunto de opciones (opciones de multicast) que se entregan a estas
llamadas al sistema, y que el kernel debe entender.
Estos son los prototipos de las funciones
setsockopt()
getsockopt()
:
int getsockopt(int s, int level, int optname, void*
optval, int* optlen);
int setsockopt(int s, int level, int optname, const void* optval, int
optlen);
El primer parámetro, s
, es el socket al que se aplica la
llamada al sistema. Para multicast, debe ser un socket de la familia
AF_INET
y puede ser del tipo SOCK_DGRAM
o
SOCK_RAW
. Se utiliza comúnmente el socket de tipo
SOCK_DGRAM
, pero si quiere implementar un demonio de
encaminamiento, o modificar uno existente, posiblemente tenga que usar
los sockets SOCK_RAW
.
El segundo, nivel
[level, n. del t.],
identifica la capa que
debe manejar la opción, mensaje, pregunta, o como quiera
llamarlo. Así, SOL_SOCKET
es para la capa del socket,
IPPROTO_IP
para la capa IP, etc... Para programación
multicast, level
será siempre IPPROTO_IP
.
La variable optname
identifica la opción que se quiere
conocer/modificar. Su valor (el que entrega el programa o devuelve
el kernel) es optval
. Los nombres posibles para ésta
variable, y que aparecen en programación multicast son los siguientes:
setsockopt() getsockopt()
IP_MULTICAST_LOOP sí sí
IP_MULTICAST_TTL sí sí
IP_MULTICAST_IF sí sí
IP_ADD_MEMBERSHIP sí no
IP_DROP_MEMBERSHIP sí no
La siguiente, optlen
, indica el tamaño de la estructura de
datos a la que apunta optval
. Observe que en
getsockopt()
éste es un resultado devuelto más que un
parámetro: el kernel escribe el valor de optname
en el buffer
apuntado por optval
, y nos indica su tamaño a través de
optlen
.
Tanto setsockopt()
como getsockopt()
devuelven 0
en caso de éxito o -1 si ha habido un error.
Tendrá que decidir, como escritor de aplicaciones, si quiere que los datos que envía sean enviados a su máquina a través del «loopback» o no. Si quiere tener más de un proceso de usuario «escuchando», se debe activar el loopback. Por otro lado, si está enviando las imágenes que produce su cámara de vídeo, posiblemente no quiera loopback, aunque quiera verse a sí mismo en la pantalla. En este caso, su aplicación probablemente recibe las imágenes de un dispositivo conectado a su ordenador y las envíe a un socket. Ya que la aplicación ya «tiene» estos datos, es improbable que quiera recibirlos de nuevo en el socket. El loopback está activado por defecto.
Observe que optval
es un puntero. No puede escribir:
setsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, 0,1);
para desactivar el loopback tienes que hacer:
u_char loop; setsockopt(socket, IPPROTO_IP,
IP_MULTICAST_LOOP, &loop, sizeof(loop));
y poner loop
a 1 para activar el loopback o a 0 para
desactivarlo.
Para saber si en un socket está activada o desactivada la opción de reenvío hacia atrás [looping back, n. del t.] use algo así:
u_char loop; int size;
getsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop,
&size)
Si no se le especifica de otra forma, los datagramas multicast se envían con un valor por defecto de 1, para impedir que sean reenviados más allá de la red local. Para cambiar el TTL al valor deseado (de 0 a 255), debe poner ese valor en una variable (aquí la llamo «ttl») y escribirla en algún lugar de su programa:
u_char ttl; setsockopt(socket, IPPROTO_IP,
IP_MULTICAST_TTL, &ttl, sizeof(ttl));
El comportamiento con getsockopt()
es similar al visto en
IP_MULTICAST_LOOP.
Generalmente, el administrador del sistema especifica el interfaz por el que se envían, por defecto, los datagramas multicast. El programador puede modificar esto y elegir un interfaz concreto para un socket determinado con esta opción.
struct in_addr interface_addr; setsockopt (socket,
IPPROTO_IP, IP_MULTICAST_IF, &interface_addr,
sizeof(interface_addr));
De ahora en adelante, todo el tráfico multicast generado en este
socket, será enviado a través del interfaz generado. Para volver al
comportamiento original y permitir al kernel elegir el interfaz de
salida basado en la configuración del administrador del sistema, es
suficiente llamar a setsockopt()
con esta misma opción e
INADDR_ANY
en el campo de interfaz.
Para elegir el interfaz de salida, las siguientes ioctl
s
pueden ser útiles: SIOCGIFADDR
(para obtener la dirección de
un interfaz), SIOCGIFCONF
(para obtener una lista con todos
los interfaces) y SIOCGIFFLAGS
(para obtener las flags de un
interfaz, y, por tanto determinar si el interfaz tiene capacidades
multicast -el flag IFF_MULTICAST
-).
Si el ordenador tiene más de un interfaz y la opción IP_MULTICAST_IF
no está fijada, las transmisiones multicast se envían a través del
interfaz por defecto, aunque los interfaces restantes pueden usarse
para reenvío
multicast si el ordenador está actuando como un
encaminador multicast.
Recordemos que se necesita decir al kernel en qué grupos multicast
está uno interesado. Si ningún proceso está interesado en un grupo,
los paquetes destinados a éste que llegan al ordenador son
descartados. Para informar al kernel de sus intereses y, por tanto,
convertirse en miembro de ese grupo, debe primero llenar una
estructura ip_mreq
que se pasa más tarde al kernel en el
campo optval
de la llamada al sistema setsockopt()
.
La estructura ip_mreq (obtenido de /usr/include/linux/in.h
)
tiene los siguientes miembros:
struct ip_mreq {
struct in_addr imr_multiaddr; /* Direccion multicast IP del
grupo */
struct in_addr imr_interface; /* Direccion IP local del
interfaz */ };
(Observación: la definición «física» de la estructura está en el
fichero arriba indicado. Sin embargo, no debería incluir
<linux/in.h>
si quiere que su código sea portable. En su
lugar incluya <netinet/in.h>
que incluye a su vez a
<linux/in.h>
).
El primer miembro, imr_multiaddr
, mantiene la dirección del
grupo al que deseas unirse. Recuerde que las asociaciones también
están relacionadas con el interfaz, no sólo con los grupos. Esta es la
razón por la que tienes que dar un valor al segundo miembro:
imr_interface
. De esta forma, si está en un ordenador
«multihomed», puede unirse al mismo grupo en distintos
interfaces. Siempre puede llenar el último miembro con la dirección
comodín (INADRR_ANY
) y entonces el kernel se encargará de
elegir el interfaz apropiado.
Con esta estructura rellena (supongamos que la ha definido como
struct ip_mreq mreq;
) sólo tienes que llamar a
setsockopt()
de la siguiente manera:
setsockopt (socket, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq));
Observe que puede unirse a distintos grupos en el mismo socket, no
sólo a uno. El límite a esto es IP_MAX_MEMBERSHIPS
y, en la
versión 2.0.33, tiene el valor de 20.
El proceso es muy similar al de unirse a un grupo:
struct ip_mreq mreq; setsockopt (socket, IPPROTO_IP,
IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
Donde mreq
es la misma estructura con los mismos datos usados
para unirse al grupo. Si el imr_interface
está rellenado con
INADRR_ANY
, el primer grupo que encaje es abandonado.
Si se ha unido a muchos grupos en el mismo socket, no necesita abandonar todos los grupos para terminar. Cuando cierra un socket, todas las asociaciones de éste son abandonadas por el kernel. Lo mismo ocurre si el proceso que abrió el socket muere.
Finalmente, recuerde que porque un proceso abandone un grupo eso no
significa que el ordenador deje de recibir datagramas para ese
grupo. Si otro socket se unió al grupo en el mismo interfaz
previamente a este IP_DROP_MEMBERSHIP
, entonces el
ordenador
seguirá siendo un miembro del grupo.
Tanto ADD_MEMBERSHIP como DROP_MEMBERSHIP son operaciones no bloqueantes. Deberían retornar inmediatamente indicando éxito o fallo.