Получение нескольких многоадресных каналов на одном порту – C, Linux

У меня есть приложение, которое принимает данные из нескольких многоадресных источников на одном и том же порту. Я могу получить данные. Тем не менее, я пытаюсь учитывать статистику каждой группы (т. Е. Полученные сообщения, полученные байты), и все данные смешиваются. Кто-нибудь знает, как решить эту проблему? Если я попытаюсь посмотреть адрес отправителя, это не адрес многоадресной рассылки, а IP-адрес отправляющей машины.

Я использую следующие параметры сокета:

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)); 

а также:

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

[Отредактировано, чтобы уточнить, что bind() может фактически включать многоадресный адрес.]

Таким образом, приложение объединяет несколько групп многоадресной передачи и принимает сообщения, отправленные любому из них, в один и тот же порт. SO_REUSEPORT позволяет связывать несколько сокетов с одним и тем же портом. Помимо порта, bind() нуждается в IP-адресе. INADDR_ANY – это INADDR_ANY адрес, но также может использоваться IP-адрес, в том числе многоадресный. В этом случае только пакеты, отправленные на этот IP-адрес, будут доставлены в сокет. Т.е. вы можете создать несколько сокетов, по одному для каждой группы мультивещания. bind() каждый сокет для (group_addr, port) и join group_addr. Затем данные, адресованные различным группам, будут отображаться в разных сокетах, и вы сможете отличить их таким образом.

Я тестировал, что следующие работы над 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); } } 

Если вы запускаете несколько таких процессов, для разных адресов многоадресной рассылки и отправляете сообщение на один из адресов, только соответствующий процесс получит его. Конечно, в вашем случае вы, вероятно, захотите иметь все сокеты в одном процессе, и вам нужно будет использовать select poll или эквивалент, чтобы прочитать их все.

Спустя несколько лет, столкнувшись с этим странным поведением в Linux, и используя обходное решение bind, описанное в предыдущих ответах , я понимаю, что справочная страница ip (7) описывает возможное решение:

IP_MULTICAST_ALL (начиная с Linux 2.6.31)
Этот параметр можно использовать для изменения политики доставки многоадресных сообщений в сокеты, привязанные к подстановочному адресу INADDR_ANY. Аргумент – логическое целое число (по умолчанию – 1). Если установлено значение 1, сокет будет получать сообщения от всех групп, которые были объединены глобально по всей системе. В противном случае он будет отправлять сообщения только из групп, которые были явно соединены (например, с помощью опции IP_ADD_MEMBERSHIP) в этом конкретном сокете.

Затем вы можете активировать фильтр для приема сообщений объединенных групп, используя:

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

Эта проблема и способ ее решения, позволяющий IP_MULTICAST_ALL обсуждается в Redhat Bug 231899 , в этом обсуждении содержатся тестовые программы для воспроизведения проблемы и ее решения.

Используйте setsockopt() и IP_PKTINFO или IP_RECVDSTADDR зависимости от вашей платформы, предполагая IPv4. Это в сочетании с recvmsg() или WSARecvMsg() позволяет вам найти адрес источника и получателя для каждого пакета.

Unix / Linux, обратите внимание, что FreeBSD использует IP_RECVDSTADDR а оба поддерживают IP6_PKTINFO для IPv6.

Windows также имеет IP_ORIGINAL_ARRIVAL_IF

замещать

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

с

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

это помогает мне (linux), для каждого приложения я получаю отдельный stream mcast из отдельной группы mcast на одном порту.

Также вы можете посмотреть в источник проигрывателя VLC, он показывает много каналов mcast iptv из разных групп mcast на одном порту, но я не знаю, как он разделяет канал.

Мне пришлось использовать несколько сокетов, каждый из которых смотрел разные групповые адреса многоадресной рассылки, а затем подсчитывал статистику по каждому сокету отдельно.

Если есть способ увидеть «адрес получателя», как указано в ответе выше, я не могу понять это.

Один важный момент, который также занял меня некоторое время – когда я привязывал каждый из своих индивидуальных сокетов к пустому адресу, как это делают большинство примеров python:

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

Я получил все многоадресные пакеты (из всех групп многоадресной рассылки) в каждом сокете, что не помогло. Чтобы исправить это, я привязал каждый сокет к своей собственной многоадресной группе

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

И это сработало.

IIRC recvfrom () дает вам другой адрес / порт для каждого отправителя.

Вы также можете поместить заголовок в каждый пакет, идентифицирующий отправителя источника.

Адрес многоадресной рассылки будет адресом получателя, а не адресом отправителя в пакете. Посмотрите на IP-адрес получателя.

Вы можете разделить streamи многоадресной рассылки, просмотрев IP-адреса получателей принятых пакетов (которые всегда будут адресами многоадресной передачи). Это несколько связано с этим:

Привяжите к INADDR_ANY и установите IP_PKTINFO сокета IP_PKTINFO . Затем вы должны использовать recvmsg() для приема ваших многоадресных UDP-пакетов и для сканирования управляющего сообщения IP_PKTINFO . Это дает вам некоторую информацию о боковой полосе принятого пакета UDP:

 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 */ }; 

Посмотрите на ipi_addr: это будет многоадресный адрес только что полученного пакета UDP. Теперь вы можете обрабатывать принятые пакеты, специфичные для каждого многоадресного streamа (многоадресного адреса), который вы получаете.