Recepción de múltiples transmisiones de multidifusión en el mismo puerto: C, Linux

Tengo una aplicación que está recibiendo datos de múltiples fonts de multidifusión en el mismo puerto. Puedo recibir los datos. Sin embargo, estoy tratando de dar cuenta de las estadísticas de cada grupo (es decir, los mensajes recibidos, los bytes recibidos) y todos los datos se mezclan. ¿Alguien sabe cómo resolver este problema? Si trato de ver la dirección del remitente, no es la dirección de multidifusión, sino la dirección IP de la máquina de envío.

Estoy usando las siguientes opciones de socket:

struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr("224.1.2.3"); mreq.imr_interface.s_addr = INADDR_ANY; setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); 

y también:

 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)); 

[Editado para aclarar que bind() puede de hecho incluir una dirección de multidifusión.]

Por lo tanto, la aplicación está uniendo varios grupos de multidifusión y recibiendo mensajes enviados a cualquiera de ellos al mismo puerto. SO_REUSEPORT permite vincular varios sockets al mismo puerto. Además del puerto, bind() necesita una dirección IP. INADDR_ANY es una dirección general, pero también se puede usar una dirección IP, incluida una de multidifusión. En ese caso, solo los paquetes enviados a esa IP se entregarán al socket. Es decir, puede crear varios sockets, uno para cada grupo de multidifusión. bind() cada socket al (group_addr, port), AND join group_addr. Luego, los datos dirigidos a diferentes grupos aparecerán en diferentes tomas, y podrá distinguirlo de esa manera.

Probé que los siguientes trabajos en FreeBSD:

 #include  #include  #include  #include  #include  #include  #include  #include  int main(int argc, const char *argv[]) { const char *group = argv[1]; int s = socket(AF_INET, SOCK_DGRAM, 0); int reuse = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) == -1) { fprintf(stderr, "setsockopt: %d\n", errno); return 1; } /* construct a multicast address structure */ struct sockaddr_in mc_addr; memset(&mc_addr, 0, sizeof(mc_addr)); mc_addr.sin_family = AF_INET; mc_addr.sin_addr.s_addr = inet_addr(group); mc_addr.sin_port = htons(19283); if (bind(s, (struct sockaddr*) &mc_addr, sizeof(mc_addr)) == -1) { fprintf(stderr, "bind: %d\n", errno); return 1; } struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr(group); mreq.imr_interface.s_addr = INADDR_ANY; setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); char buf[1024]; int n = 0; while ((n = read(s, buf, 1024)) > 0) { printf("group %s fd %d len %d: %.*s\n", group, s, n, n, buf); } } 

Si ejecuta varios procesos de este tipo, para diferentes direcciones de multidifusión y envía un mensaje a una de las direcciones, solo el proceso pertinente lo recibirá. Por supuesto, en su caso, es probable que desee tener todos los sockets en un solo proceso, y deberá usar select o poll o equivalente para leerlos todos.

Después de algunos años enfrentando este extraño comportamiento de Linux y usando la solución de enlace descrita en respuestas anteriores, me doy cuenta de que la página de manual de ip (7) describe una posible solución:

IP_MULTICAST_ALL (desde Linux 2.6.31)
Esta opción se puede usar para modificar la política de entrega de los mensajes de multidifusión a los sockets vinculados a la dirección INADDR_ANY del comodín. El argumento es un entero booleano (por defecto es 1). Si se establece en 1, el socket recibirá mensajes de todos los grupos que se han unido globalmente en todo el sistema. De lo contrario, entregará mensajes solo de los grupos que se han unido explícitamente (por ejemplo, a través de la opción IP_ADD_MEMBERSHIP) en este socket en particular.

Luego puede activar el filtro para recibir mensajes de grupos unidos usando:

 int mc_all = 0; if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_ALL, (void*) &mc_all, sizeof(mc_all))) < 0) { perror("setsockopt() failed"); } 

Este problema y la forma de resolverlo habilitando IP_MULTICAST_ALL se discute en Redhat Bug 231899 , esta discusión contiene progtwigs de prueba para reproducir el problema y resolverlo.

Utilice setsockopt() y IP_PKTINFO o IP_RECVDSTADDR dependiendo de su plataforma, suponiendo IPv4. Esto combinado con recvmsg() o WSARecvMsg() permite encontrar la dirección de origen y destino de cada paquete.

Unix / Linux, note FreeBSD usa IP_RECVDSTADDR mientras que ambos soportan IP6_PKTINFO para IPv6.

Windows, también tiene IP_ORIGINAL_ARRIVAL_IF

Reemplazar

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

con

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

es una ayuda para mí (Linux), para cada aplicación recibo transmisión mcast por separado de un grupo de difusión independiente en un puerto.

También puedes ver el origen del reproductor VLC, muestra muchos canales mcast iptv de diferentes grupos de mcast en un puerto, pero no sé cómo separe el canal.

Tuve que usar varios sockets cada uno mirando direcciones de grupo de multidifusión diferentes, y luego contar las estadísticas en cada socket individualmente.

Si hay una manera de ver la “dirección del receptor” como se menciona en la respuesta anterior, no puedo resolverlo.

Un punto importante que también me llevó un tiempo: cuando vinculé cada uno de mis sockets individuales a una dirección en blanco como la mayoría de los ejemplos de Python:

 sock[i].bind(('', MC_PORT[i]) 

Obtuve todos los paquetes de multidifusión (de todos los grupos de multidifusión) en cada socket, lo que no ayudó. Para solucionar esto, ligé cada socket a su propio grupo de multidifusión

 sock[i].bind((MC_GROUP[i], MC_PORT[i])) 

Y luego funcionó.

IIRC recvfrom () le proporciona una dirección de lectura / puerto diferente para cada emisor.

También puede poner un encabezado en cada paquete identificando el remitente de origen.

La dirección de multidifusión será la dirección del receptor, no la dirección del remitente en el paquete. Mira la dirección IP del receptor.

Puede separar las transmisiones de multidifusión mirando las direcciones IP de destino de los paquetes recibidos (que siempre serán las direcciones de multidifusión). Es algo involucrado para hacer esto:

Enlace a INADDR_ANY y configure la opción de socket IP_PKTINFO . Luego debe usar recvmsg() para recibir sus paquetes UDP de multidifusión y para buscar el mensaje de control IP_PKTINFO . Esto le da información de banda lateral del paquete UDP recibido:

 struct in_pktinfo { unsigned int ipi_ifindex; /* Interface index */ struct in_addr ipi_spec_dst; /* Local address */ struct in_addr ipi_addr; /* Header Destination address */ }; 

Mire ipi_addr: Esta será la dirección de multidifusión del paquete UDP que acaba de recibir. Ahora puede manejar los paquetes recibidos específicos para cada flujo de multidifusión (dirección de multidifusión) que está recibiendo.